오봉이와 함께하는 개발 블로그

Spring - 컴포넌트 스캔(필터, 중복 등록과 충돌) 본문

BE/Spring

Spring - 컴포넌트 스캔(필터, 중복 등록과 충돌)

오봉봉이 2022. 6. 9. 00:47
728x90

필터

  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상 지정

테스트

어노테이션 생성 -> 컴포넌트 스캔 대상에 추가

package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
@MyIncludeComponent
public class BeanA {
}

어노테이션 생성 -> 컴포넌트 스캔 대상에 제외

package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {
}
@MyExcludeComponent
public class BeanB {
}

테스트 코드

import static org.assertj.core.api.Assertions.*;
import static org.junit.jupiter.api.Assertions.*;

public class ComponentFilterAppConfigTest {
    @Test
    void filterScan() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);
        BeanA beanA = ac.getBean("beanA", BeanA.class);
        assertThat(beanA).isNotNull();

        assertThrows(NoSuchBeanDefinitionException.class,
                () -> ac.getBean("beanB", BeanB.class));
    }

    @Configuration
    @ComponentScan(
            includeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
            excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class)
    )
    static class ComponentFilterAppConfig {

    }
}
  • includeFilters에 MyIncludeComponent 어노테이션 추가해서 BeanA가 스프링 빈에 등록됨.
  • excludeFilters에 MyExcludeComponent 어노테이션 추가해서 BeanB가 스프링 빈에 등록되지 않음.

BeanA도 빼기 위한 코드

 @ComponentScan(
includeFilters = {
             @Filter(type = FilterType.ANNOTATION, classes = MyIncludeComponent.class),
       },
excludeFilters = {
             @Filter(type = FilterType.ANNOTATION, classes = MyExcludeComponent.class),
             @Filter(type = FilterType.ASSIGNABLE_TYPE, classes = BeanA.class)
       }
 )

FilterType 옵션

  • ANNOTATION : 기본값, 어노테이션을 인식해서 동작
    • 예) org.example.SomeAnnotation
  • ASSIGNBLE_TYPE : 지정한 타입과 자식 타입 인식해서 동작
    • 예) org.example.SomeClass
  • ASPECTJ : AspectJ 패턴 사용
    • 예) org.example..*Service+
  • REGEX : 정규 표현식
    • 예) org.example.Default.*
  • CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리
    • 예) org.example.MyTypeFilter

권장

@Component면 충분해서 includeFilters를 사용할 일은 거의 없지만, excludeFilters는 간혹 사용한다.
스프링 부트는 컴포넌트 스캔을 기본 제공하기 때문에 옵션을 변경하면서 사용하기 보단 기본 설정에 따라 사용하는 것을 권장

중복 등록과 충돌

컴포넌트 스캔에서 같은 빈이 등록될 경우가 두가지 존재한다

  1. 자동 빈 등록 & 자동 빈 등록
  2. 수동 빈 등록 & 자동 빈 등록

자동 빈 등록 & 자동 빈 등록

컴포넌트 스캔으로 자동 빈 등록이 되는데 이름이 같은 경우 오류를 발생시켜준다

org.springframework.beans.factory.BeanDefinitionStoreException: Failed to parse configuration class [hello.core.AutoAppConfig]; nested exception is org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'service' for bean class [hello.core.order.OrderServiceImpl] conflicts with existing, non-compatible bean definition of same name and class [hello.core.member.MemberServiceImpl]

수동 빈 등록 & 자동 빈 등록

@Component
public class MemoryMemberRepository implements MemberRepository{
  /*
    ...
  */
}
public class AutoAppConfig {
    @Bean(name = "memoryMemberRepository")
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}
  • 해당 경우 수동 빈 등록(@Bean 어노테이션 사용이 우선권을 가진다)
    • 수동 빈이 자동 빈을 오버라이딩 하기 때문.

로그

Overriding bean definition for bean 'memoryMemberRepository' with a different
  definition: replacing

문제와 해결

의도적으로 이런 결과를 기대했다면 수동이 우선권을 가지는 것이 맞다.
하지만 현실은 의도를 갖기보단 여러 설정이 꼬여 이런 결과가 만들어지는 것이 대부분이다.
저런 상황이 닥치면 해결하기 어려운 버그가 만들어지는데, 이 문제 때문에 최근 스프링 부트에선 수동 빈 등록과 자동 빈 등록이 충돌되면 오류가 발생하도록 값을 바꾸었다.

로그

스프링 부트를 실행해야 뜨는 로그이다.

Description:

The bean 'memoryMemberRepository', defined in class path resource [hello/core/AutoAppConfig.class], could not be registered. A bean with that name has already been defined in file [/Users/gobyeongchae/Desktop/SpringDir/core/out/production/classes/hello/core/member/MemoryMemberRepository.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

application.properties에서 spring.main.allow-bean-definition-overriding=true를 입력하면 실행은 가능하다(물론 수동 빈 등록이 우선이다.)

교훈

개발을 할 때는 명확한 일만 해야 한다.
내가 아무리 똑똑하더라도 개발은 팀원들과 같이 하기 때문에 어중간한 상황을 만들지 않는 것이 좋다.
코드가 간결해지더라도 명확하지 않다면 차라리 코드가 더러울지언정 명확한 코드를 작성하는 것이 좋다.
어설프게 작성한 코드 때문에 버그가 생기면 버그 해결을 위해 쓰는 시간이 더 커지기 때문이다.

출처 : 인프런 김영한 지식공유자님의 스프링 완전 정복 로드맵 강의
728x90
Comments