1. 컴포넌트 스캔과 의존관계 자동 주입 시작하기
자바 코드의 @Bean이나 XML의 <bean> 등을 통해 설정 정보에 직접 등록할 스프링 빈을 나열하는 방식의 문제점
- 등록할 빈의 개수가 많아지면 일일이 등록하기 귀찮고, 설정 정보가 커지며, 누락되는 문제가 발생할 수 있음'
이 문제를 해결하기 위해 스프링은 설정 정보가 없어도 자동으로 스프링 빈을 동록하는 기능(= 컴포넌트 스캔)과 의존관계 자동으로 주입하는 기능(@Autowired 사용)을 제공함
@Configuration
@ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {
}
- 설정 정보에 @ComponentScan을 붙여 컴포넌트 스캔을 사용할 수 있음
- 참고: @ComponentScan이 붙은 설정 정보도 자동으로 등록됨 (내부에 @Component 어노테이션이 붙어 있음)
- 기존 AppConfig와는 다르게 @Bean으로 등록한 class가 없음
컴포넌트 스캔
@Component 어노테이션이 붙은 class를 스캔해 스프링 빈으로 등록함
- @Autowired 어노테이션으로 생성자에서 여러 의존관계를 자동으로 주입할 수 있음
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
a. @ComponentScan
- @ComponentScan 어노테이션이 붙은 모든 class를 스프링 빈으로 등록함
- 스프링 빈의 기본 이름: class 명 (맨 앞글자는 소문자로 고정)
- @Component(name = "memberService")로 이름 부여 가능
b. @Autowired 의존관계 자동 주입
- 생성자에 @Autowired를 지정하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입함
- 생성자에 파라미터가 많아도 다 찾아서 자동으로 주입
- 기본적으로 타입이 같은 빈을 찾아서 주입함
- ac.getBean(xxx.class)와 비슷하게 동작
2. 탐색 위치와 기본 스캔 대상
탐색할 패키지의 시작 위치 지정
필요한 위치부터 탐색하도록 시작 위치를 지정 가능
@ComponenetScan(beanPackages = "spring.demo")
- 이 패키지를 포함해 하위 패키지를 모두 탐색
- {"spring.demo", "spring.start"} 처럼 여러 시작 위치를 지정할 수 있음
- 지정하지 않으면 @ComponenetScan이 붙은 설정 정보 class의 패키지가 시작 위치가 됨
굳이 패키지 위치를 지정하지 않고 설정 정보 class를 프로젝트 최상단에 두는 것을 권장함
- 스프링 부트도 이 방법을 기본으로 제공함 (@SpringBootApplication)
컴포넌트 스캔 기본 대상
어노테이션엔 상속 관계는 없지만, 스프링이 지원하는 기능을 통해 어노테이션이 특정 어노테이션을 들고 있는 것을 인식할 수 있음
- 참고: useDefaultFilters 옵션은 기본으로 켜져있고, 끄면 기본 스캔 대상들이 제외됨
a. @Component
컴포넌트 스캔에서 사용
b. @Controller
스프링 MVC 컨트롤러에서 사용
- 부가 기능: 스프링 MVC 컨트롤러로 인식
c. @Service
스프링 비즈니스 로직에서 사용
- 부가 기능은 없지만, 개발자들이 핵심 비즈니스 계층을 인식하는데 도움이 됨
d. @Repository
스프링 데이터 접근 계층에서 사용
- 부가 기능: 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환
e. @Configuration
스프링 설정 정보에서 사용
- 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리
3. 필터
includeFilters
임의의 어노테이션을 통해 컴포넌트 스캔 대상을 추가로 지정
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
- 이 어노테이션을 붙인 class는 스프링 빈에 등록됨
- @Component면 충분하기 때문에 거의 사용할 일이 없음
excludeFilters
임의의 어노테이션을 통해 컴포넌트 스캔에서 제외할 대상을 지정
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
- 이 어노테이션을 붙인 class는 스프링 빈에 등록되지 않음
- 실무에서 간혹 사용할 때가 있음
Filter Type 옵션
@ComponentScan 어노테이션이 붙은 설정 구성에 includeFilters와 excludeFilters 옵션을 붙이면 적용할 수 있음
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
static class ComponentFilterAppConfig {
}
5가지 옵션이 존재함
ANNOTATION | 기본값, 어노테이션을 인식해서 동작 | ex. org.example.SomeAnnotation |
ASSIGNABLE_TYPE | 지정한 타입과 자식 타입을 인식해 동작 | ex. org.example.SomeClass |
ASPECTJ | AspectJ 패턴 사용 | ex. org.example..*Service* |
REGEX | 정규 표현식 | ex. org\.example\.Default.* |
CUSTOM | TypeFilter라는 인터페이스를 구현해서 처리 | ex. org.example.MyTypeFilter |
4. 중복 등록과 충돌
자동 빈 등록 vs 자동 빈 등록
컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 스프링 빈의 이름이 같은 경우 스프링에서 오류를 발생시킴
ConflictingBeanDefinitionException
자동 빈 등록 vs 수동 빈 등록
수동 빈 등록과 자동 빈 등록에서 스프링 빈 이름이 충돌하는 경우, 수동 빈 등록이 우선권을 가짐
- 수동 빈이 자동 빈을 overriding함
Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing
수동 빈 등록, 자동 빈 등록 오류 시 스프링 부트 에러
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true