kimyu0218
  • [spring] 스프링 컨테이너 & 빈 (IoC/DI/라이프사이클/빈 정의/빈 스코프)
    2024년 06월 15일 19시 15분 32초에 업로드 된 글입니다.
    작성자: @kimyu0218

    스프링 컨테이너

    스프링 컨테이너는 스프링 프레임워크의 핵심으로, 어플리케이션 객체 (Bean) 을 관리한다. 즉, 스프링 컨테이너는 개발자를 대신하여 객체를 관리한다.

    • 어플리케이션 실행 시 필요한 객체 생성
    • 객체 간의 의존성 설정 및 주입
    • 객체의 생명 주기 관리

    스프링 컨테이너는 스프링 IoC 컨테이너, DI 컨테이너로 불리기도 한다. 빈에 대해 살펴보기 전에 IoC, DI, 컨테이너 라이프사이클에 대해 알아보자.
     
     

    IoC; Inversion of Control

    IoC는 객체의 제어를 코드 외부에 맡기는 것을 의미한다. 다시 말해 코드 외부 컨테이너나 프레임워크가 객체를 관리하도록 한다. 의존성을 외부에서 주입받기 때문에 객체 간의 결합도가 낮아지며, 구현에 종속되지 않아 구현체를 쉽게 교체하고 확장할 수 있어 유연성이 높다.

    • 👎 구현에 종속되는 경우
    class Mail implements DeliveryItem { ... }
    class Parcel implements DeliveryItem { ... }
    
    class DeliveryService {
      private Mail mail; // only 메일만 배송 가능
      ...
    }
    • 👍 구현에 종속되지 않는 경우
    class DeliveryService {
      private DeliveryItem deliveryItem; // 메일과 소포 등 DeliveryItem의 모든 구현체 배송 가능
      ...
    }

     

    DI; Dependency Injection

    의존성 주입은 IoC를 구현하는 방법으로, 객체의 의존성을 외부에서 주입하는 것을 말한다. 의존성 주입에는 크게 세 종류가 있다.

    1. 생성자 주입
    2. 세터 (setter) 주입
    3. 필드 (field) 주입

    이 중 권장되는 DI 방식은 생성자 주입이다. 생성자 주입은 객체가 생성될때 모든 의존성을 주입받기 때문에 불변성을 유지할 수 있다. 이는 객체의 동작을 예측하기 쉽도록 만든다.

    class DeliveryService {
      private final DeliveryRepository deliveryRepository;
      
      public DeliveryService(DeliveryRepository deliveryRepository) {
        this.deliveryRepository = deliveryRepository
      }
      ...
    }
    💡 `final` 키워드는 변수의 재할당을 방지하여 객체의 상태를 변경할 수 없도록 만든다.

     

    라이프사이클

    스프링 컨테이너의 라이프사이클은 크게 세 단계로 구분된다.

    1. 초기화 (initialization) : 스프링 컨테이너가 초기화될 때
    2. 사용 (usage) : 스프링 컨테이너가 초기화된 후
    3. 소멸 (destruction) : 스프링 컨테이너가 종료될 때

     
    초기화 단계에서는 스프링 컨테이너가 생성되고 (= 어플리케이션 객체) 정의가 로드된다.

    1. 컨테이너 생성 : `ApplicationContext`/`BeanFactory`
    2. 설정 파일 로드 : XML, 자바 기반 설정 클래스, 어노테이션 기반 설정
    3. 빈 정의 로드 : 설정 파일에 정의된 빈 정보 로드
    4. 빈팩토리 후처리 : 메타데이터를 수정하여 빈 정의 조작
    5. 빈 인스턴스 생성 (단, `@Lazy` 어노테이션이 붙지 않는 빈만!)
    6. 의존성 주입
    7. 빈 후처리 : `@PostConstruct`나 `Bean(initMethod="[INIT_METHOD]")` 등 실행 
    💡 `ApplicationContext` vs. `BeanFactory`
    • 빈을 생성하고 관리하는 컨테이너
    • `ApplicationContext` = `BeanFactory`의 모든 기능 + 부가 기능
      • 환경 변수 관리 : `Environment`를 통한 속성 관리
      • AOP 지원
    💡 `@Lazy`
    • 빈의 초기화를 실제로 해당 빈이 필요할 때까지 지연시킨다.
    • 모든 빈을 즉시 로딩하지 않기 때문에 어플리케이션 시작 시간을 단축시킨다.
    • 빈이 실제로 필요할 때까지 초기화하지 않기 때문에 자원을 절약할 수 있다.

     
    사용 단계에서는 초기화 단계에서 생성된 빈을 요청하고 (`ApplicationContext.getBean()`) 사용한다. (비즈니스 로직)
     
    소멸 단계는 어플리케이션 종료 전에 빈을 정리한다.

    1. 빈 후처리 : `@PreDestroy`나 `@Bean(destroyMethod="[DESTROY_METHOD]")` 등 실행
    2. 가비지 컬렉션 (GC) 에 의한 자원 정리
    🚨 소멸 단계는 어플리케이션이 정상적으로 종료되었을 때 (graceful shutdown) 이루어진다.

    빈은 스프링 컨테이너가 관리하는 객체다. 스프링 컨테이너가 객체를 인식하기 위해서는 빈을 정의해야 한다. 빈은 다음 세 가지 방법으로 정의된다.

    1. XML 설정 파일
    2. 자바 기반 설정 (= `@Configuration`이 붙은 클래스)
    3. 어노테이션 기반 설정

    XML 설정은 최근에 사용되지 않는 방식이므로 여기서 다루지 않을 것이다.
     

    자바 기반 설정

    자바 기반 설정은 스프링 어플리케이션의 구성 요소를 자바 코드로 정의하는 방법이다.

    • `@Configuration` : 스프링 컨테이너가 설정 클래스로 인식한다.
    • `@Bean` : 메서드가 반환하는 객체를 빈으로 등록한다. 
    @Configuration
    public class ApplicationConfig {
      @Bean
      public MemberService memberService() {
        return new MemberService(memberRepository());
      }
      
      @Bean
      public MemberRepository memberRepository() {
        return new MemberRepository();
      }
    }
    `ApplicationConfig`는 스프링의 빈 정보가 들어있는 설정 클래스다. `memberService`와 `memberRepository`가 빈으로 등록된다.

    빈의 이름은 기본적으로 메서드 이름을 따른다. 하지만 `@Bean(name="[BEAN_NAME]")`를 사용하여 이름을 지정할 수 있다.
     

    어노테이션 기반 설정

    어노테이션 기반 설정은 말 그대로 어노테이션을 사용하여 빈을 정의하는 방법이다.

    • `@Component` : 스프링 컨테이너가 자동으로 빈을 검색하고 등록한다. (component scan)
    • `@Autowired` : 스프링 컨테이너가 자동으로 의존성을 주입한다.
    💡 자바 기반 설정의 경우, 다른 빈 (= 메서드) 을 호출하여 의존성을 주입한다. 반면, 어노테이션 기반 설정에서는 `@Autowired`를 통해 스프링 컨테이너가 해당 빈을 찾아 자동으로 주입한다.
    💡 `@Autowired`는 생성자가 하나인 경우 생략할 수 있다.
    🤔 컴포넌트를 스캔하는 범위 (basePackage)
    • `@SpringBootApplication`은 내부적으로 `@ComponentScan`을 포함한다.
    • 별도로 `@ComponentScan`을 명시하지 않으면, 애플리케이션 시작 클래스(= `main`이 속한 클래스)가 위치한 패키지의 하위를 스캔한다.
    • 다른 패키지를 스캔하고 싶다면, `@ComponetScan`을 통해 설정할 수 있다.
    🤔 자동으로 의존성을 주입했을 때의 문제는 없을까?
    public interface MemberRepository { ... }
    
    @Repository
    public class MemberRepositoryImpl1 implements MemberRepository { ... }
    
    @Repository
    public class MemberRepositoryImpl2 implements MemberRepository { ... }
    
    @Service
    public class MemberService {
      private MemberRepository memberRepository;
      
      @Autowired
      public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
      }
    }
    • 어노테이션을 기반으로 자동으로 의존성을 주입할 때, 여러 구현체가 존재하면 `NoUniqueBeanDefinitionException` 예외가 발생한다.
    • 스프링 컨테이너는 `MemberRepositoryImpl1`을 주입해야 할지, `MemberRepositoryImpl2`를 주입해야 할지 알 수 없다.
    • 이를 해결하기 위해 `@Primary`, `@Qualifier`, 자바 기반 설정을 활용할 수 있다.
      • `@Primary` : 여러 빈 중 하나를 기본 빈으로 설정
      • `@Qualifier("[BEAN]")` : 특정 빈을 명시적으로 주입
    💡 스테레오타입 어노테이션
    • 스프링 프레임워크에서 특정 계층을 나타내기 위해 사용된다.
    • 즉, `@Component`의 특수한 형태로, 코드를 명확하게 전달할 수 있도록 돕는다.
    • `@Service`, `@Repository`, `@Controller`가 이에 속한다.
    🤔 스테레오타입 어노테이션은 단순히 객체의 역할을 구분하는 것일까?
    • 스테레오타입 어노테이션은 각 어노테이션마다 특별한 기능을 제공한다.
    • `@Respository` : 데이터 접근 시 발생하는 예외를 `DataAccessException`으로 변환한다. 이는 데이터 접근 계층에서 발생하는 예외를 일관된 방식으로 처리할 수 있게 해준다.
    • `@Controller` : HTTP 요청을 처리하고, 요청에 대한 응답을 제공한다.
    💡 `@Controller` vs. `@RestController`
    • `@RestController` = `@Controller` + `@ResponseBody`
    • `@RestController`는 모든 메서드에 자동으로 `@ResponseBody`를 붙여 JSON이나 XML 형식의 데이터를 반환한다.
    @Service
    class MemberService {
      private final MemberRepository memberRepository;
      
      @Autowired
      public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
      }
      ...
    }
    
    @Repository
    class MemberRepository { ... }

     

    빈 스코프

    빈 스코프는 빈이 생성되고 유지되는 방식을 결정한다. 스프링은 기본적으로 싱글톤 스코프를 사용한다. 인스턴스는 하나만 생성되어 해당 빈을 요청할 때마다 동일한 인스턴스가 재사용된다. 

    스코프생성 시점소멸 시점복수 인스턴스 가능
    singleton스프링 컨테이너 시작스프링 컨테이너 종료X
    prototype요청 발생가비지 컬렉션 정리O
    requestHTTP 요청 발생HTTP 요청 종료O
    sessionHTTP 세션 시작HTTP 세션 종료O
    이외에도 application, websocket 등이 있다.
    댓글