Chapter 4. Spring Boot의 코어 개념

김지민·2024년 10월 31일
post-thumbnail

이번 시간에는 백엔드의 기반이 될 스프링에 대해 배워봅시다!!

이번 주차 목표

  1. Spring 프레임워크와 API의 개념을 이해한다.
  2. Spring의 핵심 개념인 DI, IoC, 서블릿에 대해 알아본다.

🌱 스프링 프레임워크 기초 - Spring Framework, DI와 IoC란?

1. 스프링(Spring)이란?

스프링은 Java로 애플리케이션을 쉽게 개발할 수 있도록 도와주는 프레임워크입니다.
즉, 개발자가 복잡한 작업에 집중하지 않고 필요한 기능을 빠르게 구현할 수 있도록 도구와 규칙을 제공하는 틀입니다.

  • 자바 기반 개발에서 널리 사용되며, 복잡한 작업을 간단히 처리할 수 있는 다양한 도구를 제공합니다.
  • 특히 의존성 관리(DI)제어 흐름 관리(IoC)를 통해 개발자가 복잡한 비즈니스 로직에 집중할 수 있도록 돕습니다.

스프링을 쉽게 비유하자면?

1️⃣ 종합 요리 키트

  • 요리사가 모든 재료를 준비하고 요리법을 알아야 한다면 시간이 오래 걸리겠죠?
    스프링은 요리사가 아니라도 쉽게 요리를 만들 수 있도록, 필요한 재료와 조리법(구조와 규칙)을 제공하는 키트와 같습니다.
    • 재료: 스프링이 제공하는 기능들 (예: DI, IoC, 서블릿, 데이터 처리 등).
    • 조리법: 스프링이 정해둔 규칙과 패턴을 따라 작업.

2️⃣ 블록 조립 키트

  • 마치 레고 블록처럼 미리 만들어진 조각(컴포넌트)들을 끼워 맞춰서 원하는 애플리케이션을 만드는 느낌입니다.
    • 개발자는 각 블록(스프링 기능)을 조립하기만 하면, 복잡한 작업도 간단히 구현할 수 있습니다.

왜 스프링을 사용할까?

1️⃣ 복잡한 작업을 자동화

  • 객체를 생성하고, 서로 연결하고, 삭제하는 작업을 스프링이 대신 처리.
  • 개발자는 비즈니스 로직(실제 기능 구현)에 집중할 수 있음.

2️⃣ 코드 간소화

  • 불필요한 반복 코드를 줄이고, 중요한 부분에만 집중할 수 있게 해줌.

3️⃣ 유연성과 확장성

  • 다양한 애플리케이션(웹, API, 데이터베이스 등)에 사용할 수 있으며, 필요한 기능을 추가하기 쉽습니다.

4️⃣ 표준화된 설계 방식

  • 스프링이 정해준 규칙에 따라 개발하면, 다른 개발자와 협업하거나 유지보수하기 쉬움.

스프링의 주요 기능

1️⃣ IoC (Inversion of Control)

  • 객체 생성과 관리(초기화, 삭제 등)를 스프링이 대신 처리.
  • 개발자가 객체를 일일이 관리하지 않아도 됨.

2️⃣ DI (Dependency Injection)

  • 객체 간의 관계를 스프링이 설정해 줌.
  • 개발자는 "이 객체가 무엇과 연결될지"를 걱정하지 않아도 됨.

3️⃣ MVC 패턴 지원

  • 스프링은 Model-View-Controller 패턴을 사용해 웹 애플리케이션의 구조를 명확히 정의.
  • 프론트엔드(화면)와 백엔드(데이터 처리)가 분리되어 개발과 유지보수가 쉬움.

4️⃣ AOP (Aspect-Oriented Programming)

  • 중복되는 로직(예: 로깅, 트랜잭션 관리)을 분리해 코드 중복을 줄이고 효율성을 높임.

쉽게 이해하기 위한 예

스프링이 없는 개발

  • 객체를 직접 생성하고, 서로 연결하며, 요청을 처리하는 로직을 전부 작성해야 함.
  • 복잡한 설정 작업과 반복적인 코드가 많아짐.
UserService userService = new UserService();
DatabaseConnection dbConnection = new DatabaseConnection();
userService.setDatabaseConnection(dbConnection);
userService.processRequest(request);

스프링을 사용한 개발

  • 객체 생성과 의존성 설정을 스프링이 자동으로 처리.
  • 개발자는 주요 로직만 작성.
@Autowired
private UserService userService;

userService.processRequest(request);

2. 프레임워크와 API의 차이점

프레임워크와 API는 비슷하게 들리지만, 사용 방식과 역할에서 차이가 있습니다.

구분프레임워크API
정의코드 구조와 흐름을 미리 정의하고, 개발자가 이를 따라 작업하도록 제공되는 틀외부 소프트웨어와의 상호작용을 가능하게 하는 인터페이스
제어 흐름제어 흐름이 프레임워크에 의해 거꾸로(역전) 적용됨 (IoC)개발자가 필요한 기능을 호출 (제어 흐름은 개발자에게 있음)
예시스프링(Spring), 장고(Django)Java의 HttpClient API, JDBC API

비유

  • 프레임워크요리 레시피처럼 개발자가 따라야 하는 규칙과 구조를 제공합니다.
  • API재료를 요청하거나 상호작용하는 도구와 같습니다.

3. IoC(Inversion of Control)란?

그러면 각각의 스프링의 기능에 대해 더 자세히 알아봅시다.

IoC(Inversion of Control)는 제어의 흐름을 개발자가 아닌 스프링 컨테이너가 관리하도록 바꾸는 개념입니다.
즉, 객체를 만들고, 연결하고, 없애는 일을 스프링 컨테이너에게 맡긴다고 생각하면 됩니다.

왜 IoC가 필요할까?

과거의 개발 방식: 모든 것을 직접 관리

개발자가 직접 객체를 생성하고, 필요한 다른 객체와 연결하며, 객체를 없애는 과정까지 전부 관리해야 했습니다.

  • 문제점:
    1. 객체를 생성하고 관리하는 코드가 반복적으로 생김.
    2. 객체 간 연결(의존성)이 너무 밀접해서 수정하기 어려움.
    3. 코드가 복잡해지고, 유지보수에 시간이 많이 듦.

IoC 방식: 제어를 스프링 컨테이너에 위임

스프링에서는 객체를 직접 생성하거나 연결하지 않아도 됩니다.

  • 해결:
    1. 객체를 생성하고 연결하는 작업을 스프링 컨테이너가 처리.
    2. 개발자는 "무엇을 해야 할지"만 신경 쓰고, "어떻게 할지"는 스프링이 담당.
    3. 결합도가 낮아지고 코드가 단순화되어 유지보수가 쉬워짐.

IoC의 작동 방식

1️⃣ 클래스 정의

  • 필요한 기능을 가진 클래스(재료)를 정의합니다.
    public class OrderService {
        private PaymentService paymentService;
    }

2️⃣ 의존성 설정

  • 스프링 설정 파일(XML, Java Config) 또는 어노테이션을 사용해 객체 간 연결(의존성)을 설정합니다.
    @Autowired
    private PaymentService paymentService; // OrderService에서 PaymentService가 필요!

3️⃣ 컨테이너 관리

  • 스프링 컨테이너가 객체를 생성하고 연결하며, 필요 없으면 소멸까지 자동으로 처리합니다.
    개발자는 OrderService를 바로 사용할 수 있습니다.
    @Autowired
    private OrderService orderService;

IoC로 바뀌면 뭐가 좋아질까?

  1. 복잡성 감소
  • 객체 생성과 관리 로직이 없어지니 코드가 간단해집니다.
  1. 유지보수 용이
  • 객체 간 연결이 유연해지면서, 수정이나 추가 작업을 할 때 다른 코드에 영향을 주지 않음.
  1. 확장성 향상
  • 새로운 객체나 기능을 추가할 때 스프링 설정만 변경하면 되므로 코드 수정이 적습니다.

간단한 코드 비교

IoC 없이 직접 객체 관리

public class OrderService {
    private PaymentService paymentService;

    public OrderService() {
        this.paymentService = new PaymentService(); // 직접 객체 생성
    }

    public void processOrder() {
        paymentService.pay();
    }
}

IoC로 스프링 컨테이너에 위임

@Component
public class OrderService {
    @Autowired
    private PaymentService paymentService; // 스프링이 자동으로 주입

    public void processOrder() {
        paymentService.pay();
    }
}

4. DI(Dependency Injection)란?

다음으로는 의존성 주입(DI)에 대해 살펴보도록 하겠습니다.

DI(Dependency Injection)는 객체 간의 의존성을 외부에서 주입하는 방식입니다.
즉, 객체가 필요한 다른 객체(의존성)를 스스로 생성하지 않고, 스프링이 대신 연결해 주는 것을 의미합니다.

DI가 왜 필요할까?

  1. 결합도를 낮춤
    • 객체 간 연결이 느슨해져서, 하나의 객체를 수정해도 다른 객체에 영향을 주지 않음.
  2. 테스트와 유지보수 용이
    • 객체를 쉽게 교체하거나 테스트용 객체(mock 객체)로 대체 가능.
  3. 코드 단순화
    • 객체 생성과 연결을 스프링이 처리하니, 개발자는 주요 로직에 집중할 수 있음.

DI의 주요 방식

의존성 주입의 방식에는 대표적으로 생성자 주입, Setter 주입, 필드 주입 총 3가지 있습니다. 하나씩 살펴봅시다.

1️⃣ 생성자 주입

  • 객체 생성 시 생성자를 통해 의존성을 설정.
  • 장점: 의존성이 항상 설정되므로 안전하며, 테스트에 적합.
  • 단점: 의존성이 많으면 생성자가 복잡해질 수 있음.
@Component
public class OrderService {
    private final PaymentService paymentService;

    @Autowired
    public OrderService(PaymentService paymentService) {
        this.paymentService = paymentService; // 생성자로 주입
    }
}

2️⃣ Setter 주입

  • Setter 메서드를 통해 의존성을 주입.
  • 장점: 선택적으로 의존성을 설정 가능.
  • 단점: 의존성 누락 여부를 컴파일 시점에 확인할 수 없음.
@Component
public class OrderService {
    private PaymentService paymentService;

    @Autowired
    public void setPaymentService(PaymentService paymentService) {
        this.paymentService = paymentService; // Setter로 주입
    }
}

3️⃣ 필드 주입

  • 필드에 직접 의존성을 주입.
  • 장점: 코드가 간결함.
  • 단점: 테스트와 유지보수에 어려움이 있어 권장되지 않음.
@Component
public class OrderService {

    @Autowired
    private PaymentService paymentService; // 필드로 바로 주입
}

결론

쉽게 비유하자면,

  • 생성자 주입: 처음부터 필수 재료를 다 챙겨 요리를 시작.
  • Setter 주입: 요리를 하다 필요할 때마다 재료를 추가.
  • 필드 주입: 재료가 항상 자동으로 주방에 배치되어 있음.

DI는 객체를 스스로 생성하지 않고, 스프링이 필요한 객체를 대신 연결해 주어 결합도를 낮추고, 코드 유지보수를 쉽게 만들어 줍니다.
생성자 주입은 객체 생성 시 필요한 모든 의존성을 반드시 제공하도록 강제하기 때문에, 의존성 누락으로 생기는 문제를 컴파일 시점에 방지해줍니다. 따라서, 안전성과 테스트 용이성 측면에서 가장 권장되는 방식입니다!


5. 스프링 빈(Bean)이란?

스프링 빈(Bean)스프링 컨테이너에서 관리하는 객체를 의미합니다.

  • 빈은 스프링 컨테이너가 관리하며, 애플리케이션의 주요 구성 요소를 뜻합니다.
  • 개발자는 컨테이너에 빈을 등록하고, 컨테이너는 필요한 의존성을 자동으로 주입합니다.

✅ 스프링 빈의 특징

  • 컨테이너 관리: 빈은 스프링 컨테이너(ApplicationContext)에 의해 생성되고 관리됩니다.
  • 주요 구성 요소: 애플리케이션의 핵심 기능(서비스, DAO 등)을 담당하는 객체.
  • 싱글톤 기본 설정: 빈은 기본적으로 싱글톤(애플리케이션에서 하나의 인스턴스만 존재)으로 관리되며, 필요시 scope를 변경할 수 있습니다.

🌟 스프링 빈의 동작 과정

1️⃣ 빈 정의: 스프링 설정 파일(XML, Java Config)이나 어노테이션(@Component, @Bean)으로 정의.
2️⃣ 빈 등록: 컨테이너가 빈을 읽어 생성하고 등록.
3️⃣ 빈 사용: 컨테이너가 요청에 따라 빈을 반환하거나, 자동으로 주입.

✅ 빈 등록 방법

스프링에서는 빈을 등록하는 방식이 자동 등록명시적 등록으로 나뉩니다.

1️⃣ 묵시적 빈 정의 (@Component, @Autowired)

스프링이 자동으로 빈을 스캔하고 등록하는 방식입니다.

  • 클래스에 @Component, @Service, @Repository, @Controller를 붙이면 스프링이 빈으로 등록.
  • 의존성은 @Autowired로 주입.
@Component // 빈 자동 등록
public class MyService {
    @Autowired // 빈 의존성 자동 주입
    private MyRepository repository;

    public void execute() {
        repository.save();
    }
}

🌟 묵시적 빈 정의의 장점

  • 간단한 구조로 빠르게 빈을 등록할 수 있음.
  • 반복적인 코드 작성이 필요 없음.

2️⃣ 명시적 빈 정의 (@Configuration, @Bean)

개발자가 빈의 등록 과정을 직접 제어하는 방식입니다.

  • 클래스에 @Configuration을 붙이고, 메서드에 @Bean을 사용하여 빈을 등록.
  • 복잡한 초기화 작업이나 의존성 설정이 필요한 경우 사용.
@Configuration // 설정 클래스
public class AppConfig {
    @Bean // 명시적 빈 등록
    public MyService myService() {
        return new MyService(myRepository());
    }

    @Bean
    public MyRepository myRepository() {
        return new MyRepository();
    }
}

🌟명시적 빈 정의의 장점

  • 빈의 생성 과정을 세밀하게 제어할 수 있음.
  • 복잡한 의존성이나 초기화 작업에 적합.

📝 묵시적 빈 정의 vs 명시적 빈 정의 비교

구분묵시적 빈 정의 (@Component → @Autowired)명시적 빈 정의 (`@Configuration → @Bean)
사용 방법클래스에 어노테이션(@Component, @Service) 추가@Configuration 클래스에서 @Bean 메서드 사용
장점구조가 간단하고 반복 작업 감소생성 과정을 세밀하게 제어 가능
단점복잡한 구성에는 적합하지 않음코드가 길어질 수 있음
추천 상황간단한 서비스, 레포지토리 등록에 적합의존성 설정이나 초기화 작업이 필요한 경우 적합

결론: 스프링 빈의 역할

1️⃣ 스프링 빈은 애플리케이션의 주요 구성 요소이며, 컨테이너에서 객체 생성 및 생명 주기를 관리합니다.
2️⃣ 자동 등록은 간단하고 반복적인 작업에 적합하며, 명시적 등록은 복잡한 작업에 적합합니다.
3️⃣ DI와 결합되어, 스프링 빈은 유연성과 유지보수성을 극대화합니다.


6. 서블릿(Servlet)과 DispatcherServlet

서블릿(Servlet)이란?

서블릿(Servlet)웹 애플리케이션에서 클라이언트의 요청(HTTP Request)을 처리하고 응답(HTTP Response)을 생성하는 자바 기반의 컴포넌트입니다.
서블릿은 동적인 웹 페이지를 생성하는 데 사용되며, 클라이언트 요청에 따라 결과를 반환하는 역할을 합니다.

웹 애플리케이션에서 서블릿은 다음과 같은 역할을 합니다:
1. HTTP 요청 수신: 클라이언트가 입력하거나 클릭한 요청을 서버로 전달.
2. 비즈니스 로직 처리: 요청 데이터를 분석하고 필요한 작업 수행.
3. 응답 생성: 처리 결과를 HTML, JSON, XML 등의 형식으로 클라이언트에 반환.

서블릿의 동작 과정

  1. 사용자 요청: 클라이언트가 URL을 통해 HTTP 요청을 서버로 보냄.
  2. 서블릿 컨테이너: 요청을 받아 적합한 서블릿을 실행.
  3. 비즈니스 로직 처리: 서블릿이 데이터를 처리하거나 데이터베이스와 상호작용.
  4. 결과 반환: 처리 결과를 HTTP 응답으로 클라이언트에게 전송.

서블릿의 생명주기

  1. 초기화(init): 서블릿 컨테이너가 서블릿 객체를 생성하고 초기화.
  2. 서비스(service): HTTP 요청을 처리하며 doGet(), doPost() 메서드를 실행.
  3. 소멸(destroy): 서블릿 컨테이너가 서블릿 객체를 소멸하고 자원 해제.

Servlet Container란?

Servlet Container(서블릿 컨테이너)는 서블릿을 관리하고 실행하는 환경을 제공합니다. Servlet이 어떤 역할을 수행하는 메뉴얼이라면, Servlet 컨테이너는 해당 메뉴얼을 보고 직접 핸들링한다고 생각하시면 됩니다.

서블릿 컨테이너는 클라이언트의 요청(Request)를 받아주고 응답(Response)할 수 있게, 웹 서버와 Socket으로 통신합니다.
대표적인 서블릿 컨테이너로는 Apache Tomcat이 있으며, 다음과 같은 역할을 수행합니다:

  1. 요청 관리: 클라이언트의 요청을 받아 적절한 서블릿으로 전달.
  2. 객체 관리: 서블릿의 생성, 초기화, 실행, 소멸 등을 자동으로 관리.
  3. 네트워크 통신 지원: 클라이언트-서버 간의 네트워크 통신을 처리.
  4. 비즈니스 로직 분리: 개발자가 요청 처리에만 집중할 수 있도록 도움.

Spring의 DispatcherServlet

스프링에서 가장 중요한 서블릿은 DispatcherServlet입니다.
DispatcherServlet은 스프링 MVC의 중심으로, 클라이언트의 모든 HTTP 요청을 받아 적절한 Controller로 전달하고 처리 결과를 반환합니다. 이는 Front Controller 패턴을 구현한 서블릿으로, 모든 HTTP 요청을 받는 녀석이에요. 이후, 요청을 적절한 Controller로 전달하고, 로직을 실행한 후 응답을 생성해줍니다.

DispatcherServlet의 처리 과정

  1. 요청 수신: 클라이언트가 보낸 모든 HTTP 요청을 수신.
  2. Handler Mapping: 요청 URL에 따라 적합한 컨트롤러를 검색.
  3. Controller 실행: 컨트롤러에서 비즈니스 로직을 처리하고 결과를 반환.
  4. View Resolver: 처리 결과를 View로 렌더링하여 응답 생성.
  5. 클라이언트 응답: HTML, JSON 등의 형태로 결과를 반환.

DispatcherServlet의 특징

  • Front Controller 패턴 구현: 모든 요청을 중앙에서 관리.
  • Controller-서비스-DAO 계층 구조를 통해 코드의 모듈화와 유지보수를 용이하게 만듦.

예제 코드

@Controller
public class MyController {

    @GetMapping("/umc")
    public String hello(Model model) {
        model.addAttribute("message", "UMC Spring Fighting!");
        return "greeting";  
    }
}
  1. 클라이언트가 /umc URL을 요청하면, DispatcherServlet이 요청을 수신.
  2. DispatcherServlet의 Handler Mapping: DispatcherServlet가 해당 HTTP Request를 처리할 수 있는 적합한 컨트롤러(MyController)를 찾아 hello() 메서드를 호출.
  3. 비즈니스 로직을 처리한 결과를 View 이름(greeting)으로 반환.
  4. ViewResolver가 HTML 페이지를 렌더링하여 클라이언트에 응답.

서블릿과 스프링 빈의 관계

전체적인 흐름을 이해하기 위해, 서블릿과 스프링 빈 어떤 관계인지 살펴보고 마무리합시다!

1. 서블릿은 웹 애플리케이션의 핵심 요청-응답을 처리

  • 서블릿은 클라이언트의 HTTP 요청을 가장 먼저 수신하여 처리하는 역할을 합니다.

2. 스프링 빈은 서블릿의 요청 처리를 돕는 객체

  • 서블릿(특히 Spring의 DispatcherServlet)이 HTTP 요청을 처리할 때, 요청을 스프링 빈(Controller, Service, Repository 등)에 전달하여 필요한 작업을 수행합니다.

3. 서블릿은 스프링 애플리케이션의 일부로 포함

  • DispatcherServlet은 서블릿의 한 종류이며, 스프링 MVC에서 모든 요청을 받아 스프링 빈과 상호작용하며 작업을 처리합니다.

서블릿과 스프링 빈이 협력하는 과정

  1. 서블릿 수신
    클라이언트가 보낸 HTTP 요청은 서블릿 컨테이너(Tomcat 등)로 전달되고, 해당 요청이 DispatcherServlet으로 처리됩니다.

  2. DispatcherServlet 역할
    요청을 분석하여 적절한 스프링 빈(Controller)을 호출.

  3. 스프링 빈 처리
    Controller, Service, Repository 계층에서 비즈니스 로직을 수행하고 결과를 반환.

  4. 결과 반환
    DispatcherServlet이 처리 결과를 클라이언트에 응답.

비유로 이해하기

  1. 서블릿: 식당의 매니저

    • 손님(클라이언트)이 요청하면 매니저(서블릿)가 요청을 접수하고 주방이나 계산대로 전달.
    • 최종 결과(요리나 계산 결과)를 손님에게 전달.
  2. 스프링 빈: 식당의 직원

    • 매니저가 전달한 요청을 실제로 처리.
    • 요리사(비즈니스 로직), 서빙 직원(데이터 전달), 계산원(데이터 처리)이 모두 스프링 빈의 역할.

정리하자면,

  • 서블릿: 클라이언트 요청을 처리하고 응답을 생성하는 웹 애플리케이션의 요청-응답 처리자.
  • 스프링 빈: 애플리케이션의 비즈니스 로직과 핵심 기능을 담당하며, 스프링 컨테이너에서 관리.

둘은 역할이 다르지만, 협력하여 클라이언트 요청을 처리하고 애플리케이션의 동작을 구현합니다.


🎯 핵심 키워드 정리

오늘 배운 내용 중 핵심 키워드를 정리해보겠습니다.

  • DI (Dependency Injection): 객체 의존성을 외부에서 주입하여 결합도를 낮추는 기법.
  • IoC (Inversion of Control): 객체 생성과 제어를 개발자가 아닌 스프링 컨테이너에 위임.
  • 프레임워크 vs API: 프레임워크는 코드 흐름 제공, API는 상호작용 인터페이스 제공.
  • 스프링 빈: 스프링 컨테이너에서 관리되는 객체.
  • DispatcherServlet: 클라이언트 요청을 적절한 컨트롤러로 전달하는 서블릿.

💡 정리하며

스프링의 DI, IoC, DispatcherServlet 개념은 애플리케이션 설계의 복잡성을 줄이고 유지보수를 쉽게 만드는 핵심입니다. 이 기본 개념을 통해, 스프링 기반의 프로젝트를 체계적으로 설계할 수 있습니다.
다음 시간부터는 오늘 배운 스프링부트를 본격적으로 학습하면서 JPA에 대해 배워보도록 하겠습니다.😊

profile
열혈개발자~!!

0개의 댓글