컴포넌트 스캔
지금까지 스프링 빈을 등록할 때는 자바 코드의 @Bean 이나 XML 태그의 bean 등을 통해 설정 정보에 직접 등록할 스프링 빈을 나열했다.
이렇게 등록해야 할 스프링 빈이 수십, 수백개가 되면 일일이 등록하기도 귀찮고, 설정 정보도 커지고, 누락의 문제도 발생할 수 있다.
그래서 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다.
의존관계도 자동으로 주입하는 @Autowired 도 제공한다.
컴포넌트 스캔을 사용하려면 @ComponentScan을 설정정보에 붙여주면 된다.
이 어노테이션은 @Component를 붙인 클래스에 가서 알아서 가져오는 역할을 한다.
1
2
3
4
5
6
7
8
9
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig{
} // 내용은 아무것도 없다.
//우리가 전에 만든 AppConfig에는 @Configuration이 붙어있고
//해당 어노테이션을 타고올라가면 @Component가 존재하기 때문에 일단 테스트코드에서는 필터링 해준다.
//실무에서는 이렇게 할 일 없다고 보면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//MemberServiceImpl
@Component
public class MemberServiceImpl implements MemberService{
private final MemberRepository memberRepository;
@Autowired
public MemberServiceImpl(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}
@Override
public void join(Member member) {
memberRepository.save(member);
}
@Override
public Member findMember(Long memberId) {
return memberRepository.findById(memberId);
}
생성자에서 의존관계 주입을 해주고 있는데 기존의 AppConfig에서는 직접 구현체를 지정해주던 것을 해줄 수 없게 된다.
이 때 @Autowired를 써주면 자동으로 의존관계 주입을 해준다.
// MemoryMemberRepository, RateDiscountPolicy, MemberServiceImpl, OrderServiceImpl에 @Component 추가 한다.
MemberServiceImpl, OrderServiceImpl은 @Autowired도 추가해야 한다.
- @ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록.
- 이 때 빈의 이름은 클래스명을 사용하되 맨 앞글자만 소문자를 사용한다.
- 생성자에 @AutoWired를 지정하면, 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
기본적으로 타입이 같은 빈을 찾아서 주입한다고 보면 된다. (getBean(MemberRepository.class)
** 그런데 현재는 MemoryMemberRepository에만 @Component를 붙였기 때문에 알아서 얘만 가져와서 등록하는데 다른 MemberRepository가 있어서 중복된다면? (ex - JdbcMemberRepository) 그럴일 없나?
탐색 위치와 기본 스캔 대상
1
2
3
4
5
6
7
@Configuration
@ComponentScan(
basePackages = {"hello.core.member", "hello.core.order"}
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig{
}
basePackages 로 해당 부분만 스캔할 수 있게 설정 가능하다. 위의 코드대로면 멤버패키지와 오더패키지만 스캔한다.
지정하지 않는다면? - @ComponentScan을 붙인 클래스가 있는 폴더의 하위 폴더를 다 스캔하는 것이 디폴트.
그래서 실무에서는 패키지 위치를 지정하기보단 설정 정보 클래스의 위치를 프로젝트의 최상단에 두는 것이 관례이다.
스프링 부트를 사용한다면 스프링 부트의 대표 시작정보인 @SpringBootApplication을 프로젝트 시작 루트 위치에 두는 것이 관례이다. 이 어노테이션을 타고 들어가면 @ComponentScan이 붙어있기 때문.
Component 스캔의 기본 대상
- @Component - 컴포넌트 스캔
- @Controller - 스프링 MVC 컨트롤러
- @Service - 비즈니스 로직
- @Repository - 데이터 접근 계층
- @Configuration - 설정 정보
그래서 @Controller, @Service, @Repository 붙어있으면 알아서 빈으로 등록 되는 것이었다. 그리고 예를 들어 컨트롤러나 서비스에서 Repository가 필요할 때 @Autowired로 주입받는 것이었다.
필터
- includeFilters : 컴포넌트 스캔 대상을 추가로 지정한다.
- excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정한다.
FilterType 옵션
- ANNOTATION : 애노테이션을 인식해서 동작한다.
- ASSIGNABLE_TYPE : 지정한 타입과 자식타입을 인식해서 동작한다.
- ASPECTJ : ASPECTJ 패턴을 사용
- REGEX : 정규 표현식
- CUSTOM : TypeFilter이라는 인터페이스를 구현해서 처리
@ Component면 충분하기 때문에, 필터기능을 사용할 일은 거의 없다. 특히 최근 스프링 부트는 컴포넌트 스캔을 기본으로 제공하는데, 옵션을 변경하면서 사용하기 보다는 스프링의 기본 설정에 맞추어 사용하는 것이 권장된다.
중복등록과 충돌
- 자동 빈 등록 vs 자동 빈 등록
- 수동 빈 등록 vs 자동 빈 등록
자동 빈 vs 자동 빈
이름이 같은 경우 스프링은 오류를 발생시킨다.
ConflictingBeanDefinitionException 예외 발생.
수동 빈 vs 자동 빈
같은 이름으로 수동으로 등록한 빈과 자동으로 등록한 빈이 있으면 어떻게 되나.
수동 빈이 우선권을 가지고 자동 빈을 덮어쓴다.
의도하지 않았을 때 수동 빈이 덮어써서 버그가 발생하면 찾기 힘들다.
그래서 스프링부트는 최근 수동 빈 등록과 자동 빈 등록 충돌이나면 오류가 발생하도록 기본 값을 바꾸었다.