Notice
Recent Posts
Recent Comments
Link
관리 메뉴

look-forest

컴포넌트 스캔 - 자동 빈 등록 본문

Spring/Spring 핵심 원리

컴포넌트 스캔 - 자동 빈 등록

studyHub 2023. 4. 29. 16:29

[수동 빈 등록 방식의 한계]

지금까지는 스프링 빈을 등록할 구성 정보를 설정 파일에 직접 명시해줬다.

자바 코드의 @Bean이나 XML 등을 통해서 설정 정보에 직접 등록할 스프링 빈을 나열했다.

빈이 100개면?

이렇게 수동으로 등록해야 스프링 빈이 수십, 수백개가 되면

일일이 등록하기 번거롭고, 설정 정보도 커지고, 누락하는 문제 발생한다.

 

그래서 스프링은 설정 정보가 없어도 자동으로 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다

 


 

 

컴포넌트 스캔과 의존관계 자동 주입 개요

1. 컴포넌트 스캔을 사용하려면 먼저 @ComponentScan 설정 정보에 붙여줘야 한다

초간단! 기존의 AppConfig와는 다르게 @Bean으로 등록한 클래스가 하나도 없다!

컴포넌트 스캔을 쓰더라도 빈을 수동 등록해도 된다

2. 빈으로 등록할 클래스에 @Component를 붙여주자

컴포넌트 스캔은 이름 그대로 @Component 애노테이션이 붙은 클래스를 스캔해서 빈으로 등록한다

애노테이션만 붙이면 컴포넌트 스캔을 통해 빈으로 등록된다

 

그런데 이렇게 되면 의존관계 주입을 명시할 수 없다..

그래서 의존 관계도 자동으로 주입하는 @Autowired라는 기능도 제공한다!

@Autowired를 붙이면 컨테이너가 빈을 주입해준다

 

테스트해보자

설정 정보로 AutoAppConfig 클래스를 넘겨준다

ApplicationContext의 생성 인자로 넣으면 즉시 빈으로 등록된다.

로그를 보면 컴포넌트 스캔이 동작하는 것을 확인할 있다

ClassPathBeanDefinitionScanner - Identified candidate component class: file [~\RateDiscountPolicy.class]
DefaultListableBeanFactory - Creating shared instance of singleton bean 'rateDiscountPolicy'
DefaultListableBeanFactory - Autowiring by type from bean name 'orderServiceImpl' via constructor to bean named 'rateDiscountPolicy'

 


 

컴포넌트 스캔과 자동 의존관계 주입의 동작 방식

1. 컴포넌트 스캔

애노테이션을 보고 빈 컨테이너에 등록

- @ComponentScan @Component 붙은 모든 클래스를 스프링 빈으로 등록

- 이때 스프링 빈의 기본 이름은 클래스명을 사용하되 앞글자만 소문자를 사용

 

2. 의존관계 자동 주입

getBean(MemberRepository.class)처럼 동작

- 생성자에 @Autowired를 지정하면, 스프링 컨테이너가 자동으로 해당 빈을 찾아서 주입

- 기본 조회 전략: 타입이 같은 빈을 찾아서 주입

 


컴포넌트 스캔의 탐색 범위

모든 자바 클래스를 컴포넌트 스캔하면 시간이 오래 걸린다.

그래서 필요한 위치부터 탐색하도록 시작 위치를 지정할 있다

@ComponentScan(
 basePackages = "hello.core",
}
  • basePackages : 탐색할 패키지의 시작 위치를 지정
  • basePackageClasses : 지정한 클래스의 패키지를 탐색 시작 위치로 지정
  • default: @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
[권장 사항]
프로젝트 시작 루트에 AppConfig 같은 메인 설정 정보를 두고, @ComponentScan 붙일 것
(basePackages 지정은 생략)

※ 스프링 부트의 경우,
대표 시작 정보인 @SpringBootApplication를 프로젝트 시작 루트 위치에 두는 것이 관례이고, @SpringBootApplication 안에 @ComponentScan이 들어있다.

프로젝트 시작 루트에 대표 시작 정보인 설정 정보를 두고 그곳부터 탐색하는 게 정석 (resource, test 등은 x)

 


 

컴포넌트 스캔의 대상

컴포넌트 스캔은 @Component 뿐만 아니라 다음과 내용도 추가로 대상에 포함한다.

@Controller, @Service, @Repository, @Configuration

※ 해당 클래스의 소스 코드를 보면 @Component를 포함하고 있다.
그러나 사실 애노테이션에는 상속관계라는 것이 없다.
애노테이션이 특정 애노테이션을 들고 있는 것을 인식할 수 있는 건, 자바 언어가 지원하는 기능은 아니고
스프링이 지원하는 기능이다.

스프링은 다음 애노테이션(메타 정보) 있으면 부가 기능 수행한다.

@Configuration : 스프링 설정 정보 인식하고, 스프링 빈이 싱글톤 유지하도록 추가 처리.

@Controller : 스프링 MVC 컨트롤러 인식

@Repository : 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.

                   (DB 따라 종속적인 예외를 서비스 계층에서 수용할 있는 예외로 추상화)

@Service : 특별한 처리를 해주진 않는다.
               다만
 '핵심 비즈니스 로직이 여기에 있겠구나'라고 비즈니스 계층을 인식하는데 도움트랜잭션

 

 


 

필터

  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.

필터 사용 방법

1. 컴포넌트 스캔 대상에 추가/제외할 애노테이션 생성

특별한 처리를 하진 않았지만 include할 대상에 붙일 예정
특별한 처리를 하진 않았지만 exclude할 대상에 붙일 예정

2. 클래스에 애노테이션 적용

BeanA는 include할 의도
BeanB는 exclude할 의도

3. 설정 정보에 ComponentScan Filter 옵션 적용

애노테이션에 특별한 기능이 있는게 아니라, excludeFilter/includeFilter의 대상을 애노테이션 이름으로 '구분'할 뿐이다

- Test

BeanB는 빈으로 등록되지 않았다!

 

FilterType 옵션

- ANNOTATION: 애노테이션을 인식해서 동작(기본값)

- ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작

여러 필터를 적용할 수 있다

 


중복 등록과 충돌

컴포넌트 스캔에서 같은 이름을 등록하면 어떻게 될까?

상황 1. 자동 등록 vs 자동 등록 → Error (뭘 등록해야 해?)

상황 2. 수동 등록 vs 자동 등록 Spring은 OK, Springboot는 Error

자동 등록(ComponentScan)과 수동 등록(@Bean) 둘 다 했다

  • Spring의 경우, 수동 빈이 자동 빈을 오버라이딩 (수동 빈은 의도한 거니까 우선권을 준다)
    [로그: Overriding bean definition for bean 'memoryMemberRepository' with a different definition]
  • Springboot의 경우, 오류를 발생시킨다
    보통은 설정이 꼬여서 이런 상황이 발생하는데, 이런 명확하지 않은 상황은 잡기 어려운 버그를 만들 가능성이 높다
    [로그: A bean with that name has already been defined and overriding is disabled]

 

 

Q. 설정 클래스에 @ComponentScan을 붙이고, Springboot를 실행하면?
    @SpringbootApplication 내부에도 @ComponentScan이 있는데???

  • @ComponentScan이 여러 개일 경우 설정을 병합해서 실행하기 때문에 컴포넌트 스캔은 한 번만 실행된다.
  • 이와 별개로, 컴포넌트 스캔을 통한 빈 등록과 @Bean을 통한 수동 등록을 같이 할 때는 빈 중복 등록으로 충돌 오류.
AutoAppConfigTest(@ComponentScan), CoreApplicationTests(@SpringbootApplication) 모두 컴포넌트 스캔을 실행하는데, AutoAppConfigTest만 충돌 오류가 발생하는 이유.

AutoAppConfigTest를 사용하면 excludeFilter로 수동 빈 등록 설정 클래스가 제외되어 하기 2개의 빈이 생성됨
- fixDiscountPolicy
- rateDiscountPolicy
즉, 기본적으로 클래스 이름으로 빈이 생성된다..!

CoreApplicationTests를 실행하게 되면 사실 차이가 하나 있는데, 스프링 부트가 컴포넌트 스캔을 하기 때문에 AppConfig도 컴포넌트 스캔의 대상이 된다..! (설정을 병합해서 실행하기 때문에)
따라서 하기 3개의 빈이 생성되고,
- fixDiscountPolicy
- rateDiscountPolicy
- discountPolicy(RateDiscountPolicy)
@Autowired는 타입이 같으면 이름까지 같은 녀석을 매칭하므로 discountPolicy가 정상적으로 주입된다.

 

 

 

 

 

 


 

참고 자료 & 이미지 출처
스프링 핵심 원리(김영한 님)