Spring @ComponentScan의 includeFilters와 excludeFilters 완벽 이해
Spring 프레임워크에서 애플리케이션의 빈을 관리할 때 @ComponentScan은 중요한 역할을 한다.
일반적으로 @Component나 @Service와 같은 애노테이션이 붙은 클래스를 스캔해 스프링 컨테이너에 빈으로 등록하지만, 때로는 특정 클래스를 제외하거나 포함해야 할 상황이 생길 수 있다. 이때 사용하는 것이 바로 includeFilters와 excludeFilters이다.
하지만 두 옵션의 동작 방식과 차이를 명확히 이해하지 못하면 혼란이 생길 수 있다. 필자의 경우도 excludeFilters와 includeFilters를 사용할 때 조건이 동일하다고 생각했지만, 실제 결과는 다르게 동작하여 왜 빈 등록이 달라지는지 의문을 가진 적이 있었다.
이번 블로깅에서는 이 두 옵션의 동작 원리와 설정 방법을 명확히 정리하고, 내가 헷갈렸던 부분을 중심으로 자세히 정리해보려고 한다.
1. @ComponentScan의 includeFilters와 excludeFilters란?
@ComponentScan은 Spring에서 제공하는 애노테이션으로, 지정된 패키지를 탐색하여 스프링 빈으로 등록할 대상을 스캔한다. 기본적으로는 @Component, @Service, @Repository, @Controller와 같은 애노테이션이 붙은 클래스를 빈으로 등록한다.
그러나 모든 클래스를 등록하는 것은 비효율적이거나, 특정 클래스만 선택적으로 등록하거나 제외해야 할 때가 있다. 이를 해결하기 위해 includeFilters와 excludeFilters를 사용한다.
1.1 includeFilters
- 정의: 기본 스캔 대상이 아닌 추가적인 클래스를 강제로 빈으로 등록할 때 사용한다.
- 사용 상황: @Component가 없는 클래스라도 특정 조건에 부합하면 빈으로 등록하고자 할 때 사용한다.
1.2 excludeFilters
- 정의: 기본 스캔 대상에서 특정 조건에 맞는 클래스를 제외할 때 사용한다.
- 사용 상황: @Component와 같은 애노테이션이 붙어 있어 기본적으로 빈으로 등록될 클래스 중 일부를 제외하고자 할 때 사용한다.
2. 필터 설정 방식
Spring의 @ComponentScan에서 필터를 설정하려면 @Filter 애노테이션을 사용해야 한다. @Filter는 필터링 기준을 지정하는 속성 type과 필터 대상이 되는 classes를 설정해야 한다.
2.1 @Filter의 type 속성
@Filter의 type 속성은 스프링이 어떤 기준으로 필터링할지를 정의한다. 주로 사용되는 값은 다음과 같다:
- FilterType.ANNOTATION: 특정 애노테이션을 기준으로 필터링
- FilterType.ASSIGNABLE_TYPE: 특정 클래스 타입을 기준으로 필터링
- FilterType.REGEX: 클래스 이름의 정규식을 기준으로 필터링
- FilterType.CUSTOM: 사용자 정의 클래스를 이용해 필터링
3. 코드 예제: includeFilters와 excludeFilters 사용하기
다음은 @ComponentScan에서 includeFilters와 excludeFilters를 동시에 사용하여, 특정 애노테이션이 붙은 클래스만 등록하거나 제외하는 예제이다.
3.1 주요 클래스 및 애노테이션 정의
먼저, 필터링 기준이 되는 애노테이션을 정의한다.
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyIncludeComponent {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyExcludeComponent {
}
위 애노테이션은 각각 등록 대상 클래스와 제외 대상 클래스를 지정하는 역할을 한다.
3.2 테스트용 클래스 생성
@MyIncludeComponent
public class BeanA {
// 빈 등록 대상
}
@MyExcludeComponent
public class BeanB {
// 빈 등록 제외 대상
}
위 코드에서 BeanA는 @MyIncludeComponent 애노테이션이 붙어 있어 빈으로 등록될 예정이고, BeanB는 @MyExcludeComponent 애노테이션이 붙어 있어 제외될 것이다.
3.3 ComponentScan 설정
@ComponentScan을 사용하여 필터링 조건을 설정한다.
@Configuration
@ComponentScan(
includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
)
public class ComponentFilterAppConfig {
}
- includeFilters: @MyIncludeComponent 애노테이션이 붙은 클래스만 강제로 빈으로 등록한다.
- excludeFilters: @MyExcludeComponent 애노테이션이 붙은 클래스는 스캔 대상에서 제외한다.
3.4 테스트 코드 작성
public class ComponentFilterAppConfigTest {
@Test
void filterScan() {
ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
// BeanA는 스프링 컨테이너에 등록된다.
BeanA beanA = ac.getBean("beanA", BeanA.class);
Assertions.assertThat(beanA).isNotNull();
// BeanB는 스프링 컨테이너에 등록되지 않으므로 예외가 발생한다.
assertThrows(
NoSuchBeanDefinitionException.class,
() -> ac.getBean("beanB", BeanB.class)
);
}
}
4. 트러블 슈팅 부분(헷갈렸던 부분): 왜 BeanB만 등록되지 않았는가?
처음에는 @MyIncludeComponent와 @MyExcludeComponent 모두 동일한 방식으로 애노테이션을 정의했기 때문에 두 클래스(BeanA, BeanB)가 동일하게 빈으로 등록될 것으로 생각했다. 하지만 실제로는 다음과 같은 차이가 있었다.
- Spring의 excludeFilters 동작 원리
- excludeFilters는 내부적으로 스캔 과정에서 제외되도록 설정하는 로직이 이미 구현되어 있다.
- 따라서 @MyExcludeComponent가 붙어 있는 BeanB는 애초에 스프링 컨테이너에 등록되지 않는다.
- 빈 등록 순서
- Spring은 먼저 excludeFilters를 적용하여 제외 대상을 걸러낸 뒤, 나머지 클래스 중 includeFilters 조건에 부합하는 것을 추가적으로 등록한다.
총 정리
includeFilters와 excludeFilters는 스프링 컨테이너에 등록할 빈의 스캔 범위를 제한하거나 확장하는 데 유용하다. 특히 excludeFilters는 스프링이 기본적으로 제공하는 필터링 로직을 활용해 특정 클래스를 스캔 대상에서 제외하도록 설계되어 있다.
내가 헷갈렸던 이유는 includeFilters와 excludeFilters가 동등하게 작동할 것이라고 생각했기 때문이다. 그러나 Spring의 동작 순서를 이해한 후, excludeFilters는 스캔 대상 자체를 배제하고, includeFilters는 추가적으로 포함시키는 역할을 한다는 것을 알 수 있었다. 이를 명확히 이해하면 보다 효과적으로 컴포넌트 스캔을 활용할 수 있을것 같다.
'JAVA > Spring' 카테고리의 다른 글
Java에서 ProcessBuilder와 클래스패스 설정에 대한 이해 (0) | 2024.12.30 |
---|---|
Spring 의존성 주입: 생성자 주입과 수정자 주입의 동작 원리 (1) | 2024.12.21 |
Spring의 ComponentScan 이해하기: @ComponentScan의 동작 원리와 활용법 (1) | 2024.12.21 |
DI(의존관계 주입, Dependency Injection)란 무엇인가? (1) | 2024.12.20 |
Spring에서 생성자에 @Autowired 어노테이션을 사용하는 이유와 장점 (0) | 2024.12.20 |
댓글