https://www.inflearn.com/roadmaps/373
⚔️컴포넌트 스캔(ComponentScan)
- 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
- 의존관계 자동으로 주입하는
@Autowired
라는 기능도 제공한다.@Autowired
같은 경우 6편에서 바로 다룬다.
AutoAppConfig 클래스를 만들어서 살펴보자.
@Configuration과 @ComponentScan 어노테이션을 달아주는데, 앞에서 사용한 AppConfig파일과 충돌하지 않게
@ComponentScan(excludeFilters = @Filter(type= FilterType.ANNOTATION, classes=Configuration.class)) 이렇게 만들어준다.
⟶ @ComponentScan은 이름 그대로 @Component가 붙은 클래스를 스캔해서 스프링 빈으로 등록되어 있다. 스프링 관련 어노테이션을 열어보면 알겠지만 대부분의 어노테이션이 @Component가 붙어있다. 그만큼 스프링이 관리하는 클래스가 많다.
⟶ 이제 이 AutoAppConfig를 테스트 해보기 위해서 RateDiscountPolicy, MemberServiceImpl, MemoryMemberRepository
에 @Component를 붙여주자.
그리고 다음과 같은 테스트코드를 작성해보자.
@Test
void basicScan(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService memberService = ac.getBean(MemberService.class);
assertThat(memberService).isInstanceOf(MemberService.class);
}
컴포넌트 스캔이 잘 동작하는 걸 확인할 수 있다.
AnnotationConfigApplicationContext
를 동일하게 사용하고 설정정보로 AppConfig가 아닌 AutoAppConfig
를 사용하는 것을 확인할 수 있다.
🏹 컴포넌트 스캔과 자동 의존관계 주입이 어떻게 동작하는지 살펴보자.
@ComponentScan은 @Component가 붙은 모든 클레스를 스프링 빈으로 등록
이때 스프링 빈의 기본 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용.
- ex> MemberServiceImpl 이라면 memberServiceImpl이 빈 이름이 된다.
@Component("memberService2")
이런 방식으로 이름을 지정할 수 있지만, 굳이 추천하지 않는다.
컴포넌트 스캔 탐색 위치와 기본 스캔 대상
@ComponentScan(basepackage="hello.core") // hello.core 하위에 있는 클래스들을 스캔한다.
@ComponentScan(basepackage={"hello.core.member", "hello.core.discount"}
//hello.core.member, hello.core.discount 하위에 있는 클래스들만 스캔해서 등록한다.
ex> hello.core.discount는 컴포넌트 스캔의 대상이 되지 않는다.
⌘ 설정 정보 클래스의 위치를 프로젝트의 최상단에 두는 것(AppConfig.class, AutoAppConfig.class)
스프링 부트도 이 방법을 기본으로 제공하고 @SpringBootApplication
을 보면 @ComponentScan
이 붙어 있는 것을 쉽게 확인할 수 있다.
👉 컴포넌트 스캔의 기본 대상
@Component
컴포넌트 스캔에 사용@Controller
스프링 MVC Controller에 사용@Service
스프링 비즈니스 로직에 사용, 특별한 처리X, 개발자들이 핵심 비즈니스 로직이 여기에 있다라고 인식하는 정도@Configuration
스프링 설정 정보에 사용, 스프링 빈이 싱글톤을 유지하도록 처리@Repository
스프링의 데이터 접근 계층에서 사용includeFilters
: 컴포넌트 스캔 대상을 추가로 지정한다.excludeFilters
: 컴포넌트 스캔에서 제외할 대상 지정.@Target(Element.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent{
}
이렇게 Exclude 어노테이션도 동일하게 만들어준다.
Include는 A클래스 파일에, Exclude는 B클래스 파일에 어노테이션을 붙여준다.
@ComponentScan( includeFilters = @Filter(type=FilterType.ANNOTATION, classes=MyIncludeComponent.class),
excludeFilters = @Filter(type=FilterType.ANNOTATION, classes=MyExcludeComponent.class))
이런 방식으로 필터를 적용시켜주면 된다.
A클래스 파일은 스프링 빈에 등록될 것이고, B클래스 파일은 스프링 빈에 등록되지 않을 것이다.
필터의 Type
@Component(excludeFilters={ @Filter(type=FilterType.ANNOTATION, classes=MyExcludeComponent.class),
@Filter(type=FilterType.ASSIGNABLE_TYPE, classes=BeanA.class)}
위와 같은 방식으로 사용한다면 A클래스도 컴포넌트 스캔에서 제외
될 것이고, 코드를 본다면 ASSIGNABLE_TYPE
이 어떻게 동작하는지도 이해할 수 있다.
➥ 필터 기능을 우선 알고 넘어가자. includeFilters는 사용할 경우가 거의 없고, excludeFilters는 간혹 사용하긴 하지만 별로 없다.
(+ 부트는 컴포넌트 스캔을 제공하는데, 옵션을 변경하기 보다는 스프링의 기본 설정에 맞추어 사용하는 것을 권장한다고 한다.)
자동 빈 등록 vs 자동 빈 등록
자동으로 스프링 빈이 등록되는 경우, 아룸아 같은 경우에 스프링은 오류를 발생시킨다.
ConfictingBeanDefinitionException 예외가 발생
수동 빈 등록 vs 자동 빈 등록
수동 빈 등록이 우선권을 가진다. 한 마디로 오버라이딩 수동이 자동을 덮어버린다.
이런 오버라이딩 경우를 알고 결과를 바라지는 않는다. 애초부터 중복이 되는 것을 막는 것이 좋다.
➥ 그래서 최근의 스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌하는 경우 오류가 발생하도록 기본 설정을 바꾸었다.
➽ 이번에는 ComponentScan
과 그 옵션들에 대해서 정리했다. 스프링을 정리할 때 어려운 부분도 있지만, 조금씩 이해하는 과정이 재밌다. 그냥 클론코딩을 할 때에 애매했던 부분이 하나씩 정리되는 기분 좋은 성취감도 있다. 다음은 의존관계 자동주입에 대해서 정리해볼 예정이다.