Bepoz
파즈의 공부 일기
Bepoz
전체 방문자
오늘
어제
  • 분류 전체보기 (232)
    • 공부 기록들 (85)
      • 우테코 (17)
    • Spring (85)
    • Java (43)
    • Infra (17)
    • 책 정리 (0)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
Bepoz

파즈의 공부 일기

Spring

[Spring] ComponentScan에 대해

2020. 10. 21. 22:57

ComponentScan에 대해

본 내용은 김영한 님의 스프링 핵심 원리 강의 내용을 토대로 정리했습니다.

스프링 빈을 등록할 때에 @Bean을 사용해서 등록하고는 했다.
하지만 이런 빈이 수십, 수백개가 되면 문제가 생길 것이다. 그래서 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔을 제공한다.

@Configuration
@ComponentScan(
        //@Configuration 내부에 Component가 있음. 우리는 Configuration 에서 Bean 으로 등록해줬기 때문에 충돌할 수 있으므로 filter로 뺐다.
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

사용하기 위해서는 @ConponentScan을 붙여주면 된다.
기존의 코드는 다음과 같았다.

@Configuration
@ComponentScan(
        //@Configuration 내부에 Component가 있음. 우리는 Configuration 에서 Bean 으로 등록해줬기 때문에 충돌할 수 있으므로 filter로 뺐다.
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
}

AutoAppConfig를 보면 @Bean으로 등록한 클래스가 없는 것을 알 수 있을 것이다.
컴포넌트 스캔은 @Component가 붙은 클래스를 스캔해서 스프링 빈으로 등록해준다.
위의 경우 excludeFilters를 붙여준 이유는 @Configuration안에도 @Component가 들어가 있는데, AppConfig에서 등록한 빈과
AutoAppConfig에서 등록되는 빈들과 충돌이 일어날 수 있어서 제외시켰다.

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository;

    @Autowired
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
}

다음과 같이 빈으로 등록할 클래스 위에 @Component를 붙여주었다. 그리고 의존관계는 @Autowired를 통해 해결해 주었다.

public class AutoAppConfigTest {

    @Test
    void basicScan(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);
        MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);

        OrderServiceImpl bean = ac.getBean(OrderServiceImpl.class);
        MemberRepository memberRepository = bean.getMemberRepository();
        System.out.println("memberRepository = " + memberRepository);
    }
}

AnnotationConfigApplicationContext를 이용해 AutoAppConfig를 넘겨주고 테스트를 실행해보았고 잘 동작한다는 것을 알 수 있었다.

@ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록한다.
빈 이름은 클래스명에서 앞글자만 소문자로 바꿔 등록하게 되는데, 따로 지정하고 싶다면 @Component("이름")이런식으로 부여하면 된다.
@Autowired를 사용하면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.
이때 조회전략의 기본은 같은 타입을 찾아 주입하는 것이다.


컴포넌트 스캔의 스캔은 지정한 클래스의 패키지를 탐색 시작 위치가 된다.(그래서 보통 프로젝트 최상단에 설정 정보를 둔다.)
@ComponentScan(basePackages="패키지명")으로 필요한 위치부터 탐색을 하도록 설정도 가능하다.

스프링 부트의 시작 정보인 @SpringBootApplication에는 @ComponentScan이 들어있기 때문에 시작 루트 위치에 두는 것이 관례이다.

@Controller,@Service,@Repository,@Configuration등은 소스코드에 @Component를 포함하고 있기 때문에,
@ComponentScan을 통해 빈 등록이 이루어질 수 있는 것이다.


컴포넌트 스캔은 스캔 대상을 지정할 수도 있고 제외할 대상을 지정할 수도 있다.(AutoAppConfig 보면 Configuration.class를 제외한 것 처럼)

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

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

@MyIncludeComponent
public class BeanA {
}

@MyExcludeComponent
public class BeanB {
}

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{

    }
}

@Component내부 코드에 있는 어노테이션들을 따서 따로 MyIncludeComponent와 MyExcludeComponent이라는 어노테이션을 생성해주었다.
그리고 BeanA와 BeanB클래스에 달아주고, ComponentFilterAppConfig에 includeFilters와 excludeFilters로 설정 해주었다.

FilterType에는 5가지 옵션이 있다.

  • ANNOTATION: 기본값, 어노테이션을 인색해서 동작한다.
  • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작한다.
  • ASPECTJ: AspectJ 패턴을 사용한다.
  • REGEX: 정규 표현식
  • CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리한다.

@Component면 충분하기 때문에 includeFilters를 사용하는 일은 거의 없다. excludeFilters또한 자주 사용하지는 않는다.


동일한 빈 이름이 등록이 되면 충돌이 발생하게 된다.

컴포넌트 스캔에 의해 자동으로 스프링 빈이 등록되는데, 그 이름이 같은 경우 ConflictionBeanDefinitionException예외가 발생하게된다.
만약 @Component를 이용한 자동 빈등록과 @Bean을 이용한 수동 빈등록의 이름이 같을 경우에는 수동 빈 등록이 자동 빈을 오버라이딩 한다.
그때의 로그 Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing

스프링 부트에서는 수동 빈과 자동 빈 등록 시에 충돌이 발생할 경우 에러가 나게끔 해준다.


'Spring' 카테고리의 다른 글

[JPA] 영속성 컨텍스트에 대해  (0) 2020.10.30
[Spring] 의존관계 주입에 대해  (0) 2020.10.22
[Spring] 빈 스코프  (0) 2020.10.21
[Spring] 빈 생명주기 콜백  (0) 2020.10.21
[Spring] Spring 싱글턴 컨테이너  (0) 2020.10.15
    'Spring' 카테고리의 다른 글
    • [JPA] 영속성 컨텍스트에 대해
    • [Spring] 의존관계 주입에 대해
    • [Spring] 빈 스코프
    • [Spring] 빈 생명주기 콜백
    Bepoz
    Bepoz
    https://github.com/Be-poz/TIL

    티스토리툴바