[기본기] 7-5. @ComponentScan.Filter, 중복 시 발생 오류

khyojun·2022년 10월 4일
1
post-thumbnail

본 게시글은 김영한님의 스프링 핵심 원리 기본편을 정리한 글입니다.


📌 @ComponentScan.Filter 를 활용하여 포함, 제외할 어노테이션 정하기

이전엔 @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. 자동 등록 vs 자동 등록
  2. 자동 등록 vs 수동 등록

자동 vs 자동

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가 있는데 빈으로 등록될때는 맨 앞의 글자는 소문자로 변경하여 등록을 하기 때문에 다음과 같이 등록이 되었다.

수동 vs 수동

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이 되었다고 알려주었다. 그리고 항상 기억해야된다고 하시는게 되도록이면 자동적으로 등록되는 것보다 수동적으로 등록되는것이 우선적으로 등록이 된다고 알려주셨다.

  • 참고 : SpringBoot에서도 @ComponentScan이 있다고 했었다. 그래서 @SpringBootApplication가 있는 곳에서 실행을 시키면?

    오류가 일어나면서 컴파일을 멈춘다. 그 이유는 그냥 스프링에서처럼 알림만 주면 솔직히 말해서 저건 오류를 띄워서 수정을 하라고 말해줄만한데 되게 애매하기 때문에 부트에서는 바로 오류를 띄워준다고 한다.

오늘의 결론

@ComponentScan의 스캔 중 필터를 활용하여 원하는것을 찾고 제외시킬 수 있다. 그리고 중복관련하여 충돌이나 오류가 일어나는 것을 조심하자. 특히! 자동 vs 수동에서!

출처

  1. 김영한님의 스프링 핵심 원리 기본편(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)
  2. https://www.baeldung.com/spring-componentscan-filter-type
  3. https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-scanning-filters
profile
코드를 씹고 뜯고 맛보고 즐기는 것을 지향하는 개발자가 되고 싶습니다

0개의 댓글