본 게시글은 김영한님의 스프링 핵심 원리 기본편을 정리한 글입니다.
이전엔 @ComponentScan을 통하여 하위에 있는 모든 @Component들을 탐색하여 빈을 자동으로 등록하였는데 이를 대신하여 만약 내가 포함하고 싶은 어노테이션이 있고 제외하고 싶은 어노테이션이 있을 경우에는 @ComponentScan.Filter를 활용하여서 이 문제를 해결할 수 있다고 하는데 한 번 예제를 통해서 알아보자.
내가 포함하고 싶은 것은 MyIncludeComponent, 제외하고 싶은 것은 MyExcludeComponent 로 어노테이션을 만들었다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
그래서 내가 BeanA는 포함시키고 BeanB를 제외시키기로 한 것이다.
@MyIncludeComponent
public class BeanA {
}
@MyExcludeComponent
public class BeanB {
}
@Test
void filterScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(ConfigTest.class);
BeanA bean = ac.getBean(BeanA.class);
Assertions.assertThat(bean).isNotNull();
org.junit.jupiter.api.Assertions.assertThrows(NoSuchBeanDefinitionException.class, () -> ac.getBean(BeanB.class));
}
@Configuration
@ComponentScan(
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class),
includeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class)
)
static class ConfigTest{
}
이렇게 해당 코드에 excludeFilters, includeFilters를 활용하여서 테스트를 진행시켜보면 BeanA는 잘 등록이 되는데 내가 assertThrows를 작성한 이유는 NoSuchBeanDefinitionException 오류가 BeanB를 getBean할때 발생하기 때문이다. 즉, BeanA는 자동적으로 빈에 등록이 되었고 BeanB는 exclude하여서 등록이 되지 않았다는 것을 알 수 있었다. 근데 문득 이런 생각이 들었다. @ComponentScan이 되긴 했어도 @Component를 작성하지 않았는데 어떻게 알아서 찾았지? 라고 생각이 들어 레퍼런스와 관련 자료들을 찾아봤는데 Filter속성을 지정을 하게 된다면 Spring에서 해당하는 조건에 맞게 자동적으로 찾아낸다고 한다.
근데 지금 위에서 적용한 방법은 Filter를 하는데 어노테이션을 활용해서 찾는 방법이었는데 이것만 있는 것은 아니고 다른 방법들도 있다.
FilterType의 옵션
- 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
- 참고 : 솔직히
@Component
만 있어도 충분해서includeFilters
는 거의 사용을 안한다고 한다. 그리고excludeFilters
도 특별한 경우가 아니면 잘 사용을 안한다고 한다. 특히 스프링 부트는 컴포넌트 스캔을 기본적으로 제공을하는데 스프링의 기본 설정에 최대한 맞추어 사용하는 것을 권장하고 선호하는 편이라고 한다.
위 부제와 같이 컴포넌트 스캔을 하다가 중복으로 등록되는 경우가 있고 충돌이 일어나는 경우도 있을거다.(자동적으로 등록을 하다보니...) 그래서 그러한 경우들을 한 번 보자.
1번과 같은 경우는 다음처럼 재연을 해봤다.
@Component("memoryMemberRepository")
public class OrderServiceImpl implements OrderService{
@Test
void basicScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService bean = ac.getBean(MemberService.class);
OrderService bean1 = ac.getBean(OrderService.class);
System.out.println("bean = " + bean);
System.out.println("bean1 = " + bean1);
Assertions.assertThat(bean).isInstanceOf(MemberService.class);
Assertions.assertThat(bean1).isInstanceOf(OrderService.class);
}
이렇게 될 경우 다음과 같은 오류가 발생한다.
왜냐하면 등록을 하는데 이미 memoryMemberRepository는 빈으로 자동 등록이 되었기 때문이다. 막간으로 복습을 해보자면 MemoryMemberRepository에도 @Component가 있는데 빈으로 등록될때는 맨 앞의 글자는 소문자로 변경하여 등록을 하기 때문에 다음과 같이 등록이 되었다.
public class AutoAppConfig {
@Bean(name = "memoryMemberRepository")
public MemberRepository memberRepository(){
return new MemoryMemberRepository();
}
}
@Test
void basicScan(){
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
MemberService bean = ac.getBean(MemberService.class);
OrderService bean1 = ac.getBean(OrderService.class);
System.out.println("bean = " + bean);
System.out.println("bean1 = " + bean1);
Assertions.assertThat(bean).isInstanceOf(MemberService.class);
Assertions.assertThat(bean1).isInstanceOf(OrderService.class);
}
이번에는 직접적으로 빈을 지정하고 하게 되면 마찬가지로 오류가 일어나겠지 하고 생각을 하였다. 그치만 오류는 아니고 알림처럼 문구가 떴는데
친절하게도 이렇게 overriding이 되었다고 알려주었다. 그리고 항상 기억해야된다고 하시는게 되도록이면 자동적으로 등록되는 것보다 수동적으로 등록되는것이 우선적으로 등록이 된다고 알려주셨다.
- 김영한님의 스프링 핵심 원리 기본편(https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8)
- https://www.baeldung.com/spring-componentscan-filter-type
- https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-filters