본문 바로가기

Backend/Spring

[Spring] 컴포넌트 스캔 2) 기타 사항

728x90

 

목차

  • 컴포넌트 스캔 - 컴포넌트 스캔과 의존관계 자동 주입 시작하기 
  • 컴포넌트 스캔 - 탐색 위치와 기본 스캔 대상
  • 컴포넌트 스캔 - 필터
  • 컴포넌트 스캔 - 중복 등록과 충돌

 

 

 

 

필터

  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정.
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정 (나름 사용됨) 

 

두가지를 사용한 예제를 만들어 보자.

scan하위에 filter패키지를 생성한다. 

 

 

먼저, annotation처럼 사용할 인터페이스들을 생성한다. 

MyExcludeComponent(인터페이스)

package hello.core.scan.filter;

import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyExcludeComponent {//이것이 붙은 것은 컴포넌트 스캔에서 제외한다
}

 

MyIncludeComponent (인터페이스)

package hello.core.scan.filter;


import java.lang.annotation.*;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyIncludeComponent {//이것이 붙은 것은 컴포넌트 스캔에 추가한다.
}

 

이제 생성한 annotation을 적용한 클래스를 생성한다.

MyIncludeComponent 추가

 

MyExcludeComponent추가

 

ComponentFilterAppConfigTest

package hello.core.scan.filter;

import hello.core.AppConfig;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

public class ComponentFilterAppConfigTest {


    @Test
    void filterScan(){
        ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);

        BeanA beanA = ac.getBean("beanA", BeanA.class); // include하여 컴포넌트 스캔대상에 포함되어 빈으로 등록되어야 정상
        assertThat(beanA).isNotNull();


//        ac.getBean("beanB", BeanB.class); //exclude하여 컴포넌트 스캔대상에서 제외되어 등록되지 않아야 정상 -> 에러
        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{

    }

}

이제 둘을 테스트 해볼 클래스를 생성한다. 

@ComponentScan을 이용하여, 제외시킬 것과 포함시킬 것을 다음과 같이 작성해준다. 

 

 

FilterType 5가지 옵션이 있다.

  • ANNOTATION: default, 애노테이션을 인식해서 동작한다(생략가능)
    •  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

 

 

중복 등록과 충돌

 

컴포넌트 스캔에서 같은 빈 이름을 등록하는 경우는

  • 자동빈등록vs자동빈등록
    • @Component("자유이름") 이거를 똑같이 지은경우 -> 거의 발생 x
  • 수동빈등록vs자동빈등록

 

자동 빈 등록 vs 자동 빈 등록


컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 스프링은 오류를 발생시킨다.

ConflictingBeanDefinitionException 예외 발생

 

수동 빈 등록 vs 자동 빈등록

이러한 오류를 발생시키기 위해, 우리가 만든 AutoAppConfig에

이미 우리가 자동으로 등록시킨 memoryMemberRepository라는 이름을 가진 수동 빈 을 생성한다. 

 

@Component 자동으로 등록된 memoryMemberRepository

 

@Component
     public class MemoryMemberRepository implements MemberRepository {}

 

수동으로 등록한 memoryMemberRepository

 

@Configuration
     @ComponentScan(
             excludeFilters = @Filter(type = FilterType.ANNOTATION, classes =
     Configuration.class)
     )
     public class AutoAppConfig {
         @Bean(name = "memoryMemberRepository")
         public MemberRepository memberRepository() {
             return new MemoryMemberRepository();
         }
}

 

오류가 발생하지 않는다. 

수동빈 등록이 우선권을 가지고, 수동 빈이 자동빈을 오버라이딩 한다.

 

스프링에서 친절하게 로그를 남겨준다.

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

 

하지만, 이런 경우는 에러가 나지 않기 때문에 의도하지 않은 잡기 어려운 버그가 만들어진다.

따라서 현재는 스프링부트가 수동빈과 자동빈 등록이 충돌이 나면 오류를 발생시키게 기본값을 바꿨으며, 
(즉 오버라이딩으로 처리되지 않음) 

스프링부트 CoreApplicaiton을 실행해 보면, 오류가 발생한다. 

 

 

만약, 오버라이딩을 해주도록 설정을 바꾸고 싶으면,

applicaiton.properties에

spring.main.allow-bean-definition-overriding=true

이를 추가해 주면된다.

 

현재 default로 false이기 때문에 에러가 발생함

728x90