본문 바로가기
공부방

[TIL 06/21] 컴포넌트 스캔, 빈 스코프, 빈 라이프사이클

by hseong 2023. 6. 21.

1. DI, Circular Dependency

Dependency Resolution Process

  • 각 빈에 대한 의존관계는 클래스의 속성이나 생성자, 정적 팩토리 메서드의 인자 형태로 표현될 수 있다.
  • 스프링 컨테이너는 컨테이너가 생성될 때 각 빈의 구성에 대한 유효성 검사를 실시한다.
  • 싱글톤 빈의 경우 스프링 컨테이너가 생성될 때 함께 생성되지만 스코프에 따라 빈이 생성되는 시점이 달라질 수 있다.
  • 또한, 지연 초기화 설정을 통해서 해당 빈이 처음으로 요청될 때 생성되게 하는 것도 가능하다.

Circular Dependency

  • 생성자 주입을 주로 사용하는 경우 해결할 수 없는 순환 종속성 시나리오가 발생할 수 있다.
  • A → B, B → A 처럼 A와 B에 대한 빈이 서로 주입되도록 구성한 경우 스프링 IoC 컨테이너는 런타임에 순환 참조를 감지하고 BeanCurrentlyInCreationException을 던진다.
  • 권장되는 방법은 아니나 setter 주입으로 순환 종속성을 구성할 순 있다.
  • A와 B 사이에 순환 종속성이 있는 경우 하나의 빈이 완전히 초기화 되기 전에 다른 빈에 주입되도록 강제하게 된다.

2. 컴포넌트 스캔

  • 스프링이 직접 클래스를 검색 해서 자동으로 빈으로 등록시켜주는 기능이다. 이를 통해 설정 정보 없이 스프링 빈을 등록할 수 있다.
    @ComponentScan
    public class AppConfig {
    }
  • @ComponentScan을 통해 이용할 수 있으며 이는 @SpringBootApplication에도 포함되어 있다.
  • @Component로 정의하면 스프링이 해당 클래스를 인식하여 빈으로 등록한다.
    • @Repository, @Service, @Controller, @Configuration은 모두 내부에 @Component를 가지고 있다.
    • @Repository: 데이터 접근 계층
    • @Service: 비즈니스 로직
    • @Controller: 스프링 MVC 컨트롤러
    • @Configuration: 스프링 설정 정보

탐색 대상 지정

  • 탐색할 대상을 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 탐색 시작 위치가 된다.
  • basePackages 속성을 이용하여 컴포넌트 스캔의 대상이 되는 패키지를 지정할 수 있다. 해당 패키지를 포함해서 하위 패키지를 모두 탐색한다.
  • basePackageClasses 속성을 이용하면 해당 클래스가 속한 패키지를 기준으로 탐색한다.
  • excludeFilters 속성을 이용하면 특정 어노테이션, 타입 등을 기준으로 스캔 대상에서 제외할 수 있다.
  • includeFilters 속성은 반대로 특정 기준을 통해 스캔 대상을 지정할 수 있다.

3. Autowired

  • 스프링 빈을 필드에 곧바로 주입해준다.
  • 외부에서 변경이 불가능해서 테스트 하기 힘들다는 단점이 있다.
  • 필드에 달아줄 수도 있고, 세터를 통해서도 가능하다.
  • 예전에는 해당 어노테이션을 생성자에 달아주어야 했다.
    • 스프링 4.3 부터 디폴트가 되었다.
    • 단, 생성자가 2개인 경우 어떤 생성자를 선택해야 하는지 모르기 때문에 원하는 생성자에@Autowired를 달아주어야 한다.

생성자 기반 의존관계 주입을 선택해야 하는 이유

  • 초기화시에 필요한 모든 의존관계가 형성되기 때문에 안전하다.
  • 잘못된 패턴을 찾을 수 있게 도와준다. 많은 파라미터를 갖는 클래스는 많은 책임을 가지고 있다는 것을 의미한다.
  • 테스트시 쉽게 목 객체를 전달할 수 있다. 세터나 필드 기반일 경우 실수로 등록이 안 된 빈에 의해 npe가 발생할 수 있다. 생정자 주입을 사용한다면 누락된 데이터가 무엇인지 빠르게 파악할 수 있다.
  • 불변성을 확보할 수 있다. 생성자 주입을 제외한 나머지 방식은 모두 생성자 이후에 호출되므로 필드에 final 키워드를 사용할 수 없다.

조회 빈이 2개 이상일 때

  • 특정 인터페이스를 구현한 클래스가 2개 이상이고 모두 빈으로 등록되어 있을 경우 예외가 발생한다.
  • 스프링은 어떤 객체를 주입해야 하는지 모르기 때문에 우선순위를 지정해주어야 한다.
    • @Primary를 달아아서 이러한 상황에서 우선적으로 사용할 빈을 선택할 수 있다.
      @Repository
      @Primary
      public class OrderMemoryRepository implements OrderRepository {
      }
    • @Qualifier를 이용할 수도 있다. @Qulifier("memory")와 같이 value를 설정하여 달아주고, 주입받길 원하는 생성자의 파라미터 선언에 @Qualifier("memory")를 붙여주면 원하는 빈을 주입받을 수 있다.
      @Repository 
      @Qualifier("memory") 
      public class OrderMemoryRepository implements OrderRepository {
      }
      public OrderService(@Qualifier("memory") OrderRepository orderRepository) {
      this.orderRepository = orderRepository
      }
  • 단, applicationContext.getBean()시에는 @Qualifier를 사용할 수 있는 방법이 없기 때문에 빈팩토리유틸이라는 별도의 방법을 이용해서 가져와야 한다. 실제로 쓸일은 거의 없다.
  • 가장 좋은 방법은 클라이언트 코드에서 이러한 걱정이 없게 하는게 좋다.
  • 특별한 경우가 아니면 여러 개의 동일한 빈을 등록하지 않거나, @Primary를 활용하도록 한다.

4. Bean Scope

  • 스프링은 다양한 스코프를 지원한다.
    • singleton: 기본값. 스프링 컨테이너 시작부터 종료까지 유지된다.
    • prototype: 스프링 컨테이너에 빈을 요청할 때마다 새로운 인스턴스를 생성해서 반환한다. 빈을 생성, 의존관계 주입, 초기화까지만 처리하고 클라이언트에 반환해주고 나면 더 이상 스프링 컨테이너가 관리하지 않는다.
    • request: HTTP 요청 하나가 들어오고 나갈 때까지 유지되는 스코프이다. 개별 요청마다 별도의 인스턴스가 생성되고, 관리된다.
    • session: HTTP Session과 동일한 생명주기를 가지는 스코프
    • application: 서블릿 컨텐스트와 동일한 생명주기를 가지는 스코프
    • websocket: 웹 소켓과 동일한 생명주기를 가지는 스코프
  • 싱글톤 빈과 프로토타입 빈을 함께 사용하는 경우 주의해야 한다. 싱글톤 빈은 생성 시점에 의존관계 주입을 받기 때문에 프로토타입 빈을 주입받을 경우 싱글톤 빈과 함께 계속 유지되어 의도한 것과 다른 동작을 수행할 수 있다.
    • 이러한 경우 applicationContext.getBean()이나 ObjectProvider로 빈을 조회해오면 매번 새로운 빈을 반환받을 수 있다.
    • 이렇게 외부에서 의존성을 주입 받는 것이 아니라 직접 의존성을 탐색하는 것을 Dependency Lookup이라고 한다.
    • getBean()의 경우 스프링 컨테이너에 종속적인 코드가 되고 단위 테스트가 어려워질 수 있다. 반면 objectProvider.getObject()를 이용하면 훨씬 간단하게 mock 객체를 생성하여 테스트를 진행하기 수월해진다.

5. Bean Lifecycle Callbacks

스프링 빈의 이벤트 라이프사이클

  • 스프링 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 → 사용 → 소멸전 콜백 → 스프링 종료
  • 스프링은 의존관계 주입이 완료된 후 한 번, 스프링 컨테이너가 종료되기 직전에 한 번 콜백을 준다.
  • 빈 생성 생명주기 콜백
    • 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
    • Bean이 InitializingBean 인터페이스 구현
    • 설정 정보에서 @Bean(initMethod = "init") 등록
    • @PostConstruct 애노테이션
  • 빈 소멸 생명주기 콜백
    • 빈이 소멸되기 직전에 호출
    • Bean이 DisposableBean 인터페이스 구현
    • 설정 정보에서 @Bean(destroyMethod = "close") 등록
    • @PreDestroy 애노테이션
  • 최신 스프링에서는 @PostConstruct, @PreDestroy 애노테이션을 이용한 방법을 권장한다.
  • 외부 라이브러리에 적용이 필요한 경우 설정 정보에서 @BeaninitMethod, destroyMethod를 사용하면 된다.
  • 이러한 콜백은 네트워크를 연결하고 연결을 종료하는 작업이 필요할 때 이를 이용할 수 있다.

'공부방' 카테고리의 다른 글

[TIL 06/23] Logging, Logback  (0) 2023.06.23
[TIL 06/22] Environment, Properties, Profile  (0) 2023.06.22
[TIL 06/20] IoC, DI, ApplicationContext  (0) 2023.06.20
[TIL 06/19]  (0) 2023.06.19
[인프런] HTTP 메서드  (0) 2023.03.29