본문 바로가기

Backend/Spring

[Spring] 컴포넌트 스캔

인프런의 <스프링 핵심 원리 - 기본편>을 듣고 공부 용도로 정리한 글 입니다. 

 

 

앞에서 @Bean을 통해서 설정 정보에 하나하나 직접 스프링 빈을 등록했던 것을 기억할 것이다.

실제 개발 환경에서는 훨씬 많은 양의 빈들을 등록할 일이 생길텐데 하나하나 등록하게 된다면 매우 귀찮고 누락되는 문제도 발생할 것이다. 

그래서 스프링에서는 설정 정보없이 자동으로 스프링 빈을 등록하는 컴포넌트 스캔이라는 기능을 제공한다. 빈 등록 뿐만이 아닌 의존관계 역시 자동으로 주입해주는 @Autowired 기능도 제공한다.

@Configuration
@ComponentScan(
        basePackages = "hello.core.member",
        basePackageClasses = AutoAppConfig.class,
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

    @Bean(name = "memoryMemberRepository")
    MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

 

 

 

우선 컴포넌스 스캔을 사용하기 위해서는 @ComponentScan을 설정 정보에 붙여줘야 한다.

ComponentScan은 이름 그대로 @Component 어노태이션이 붙은 클래스를 찾아서 스프링 빈으로 등록해준다.

 

@Component
    public class MemoryMemberRepository implements MemberRepository {}
@Component
    public class RateDiscountPolicy implements DiscountPolicy {}

 

다음과 같이 MemoryMemberRepository와 RateDiscountPolicy 클래스에 붙여 주면 @ComponentScan이 이를 찾아 스프링 빈으로 등록한다.

 

전에 작성했던 AppConfig 설정 정보에는 직접 빈을 등록하면서 의존관계까지 주입했었다.  하지만 @ComponentScan을 이용해서 빈을 등록하는 AutoAppConfig에는 의존관계에 대한 정보가 없는 것을 볼 수 있다. 

 

스프링에서는 @Autowired로 의존관계까지 자동으로 주입할 수 있다.

@Component
  public class OrderServiceImpl implements OrderService {
  
      private final MemberRepository memberRepository;
      
      private final DiscountPolicy discountPolicy;
      
      @Autowired
      public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
          this.memberRepository = memberRepository;
          this.discountPolicy = discountPolicy;
      }
}

생성자에 @Autowired를 붙여주면 스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다.

마치 getBean()을 호출할 것 처럼 말이다.

 

탐색 위치와 기본 스캔 대상

모든 클래스를 전부 조회하게 되면 너무 시간이 오래걸릴 것이다. 그래서 필요한 위치부터 탐색할 수 있도록 시작 위치를 지정할 수 있다.

@Configuration
@ComponentScan(
        basePackages = "hello.core.member",
        basePackageClasses = AutoAppConfig.class,
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {

    @Bean(name = "memoryMemberRepository")
    MemberRepository memberRepository(){
        return new MemoryMemberRepository();
    }
}

basePackages: 탐색할 패키지의 시작 위치를 지정한다. 이 패키지를 포함해서 하위 패키지를 모두 탐색한다.

basePackageClasses: 지정한 클래스의 패키지를 탐색 시작 위치로 지정한다.

만약 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.

 

* 권장하는 방식은 스프링 부트에서 기본으로 제공하는 방식이다. 설정 정보 클래스 위치를 최상단에 두고 default값으로 두는 것이다.

 

컴포넌트 스캔 기본 대상

  • @Component: 컴포넌트 스캔에서 사용
  • @Controller: 스프링 MVC 컨트롤러에서 사용, 스프링 MVC 컨트롤러로 인식
  • @Service: 스프링 비즈니스 로직에서 사용, 특별 처리 X, 비즈니스 계층을 인식하는데 도움을 줌
  • @Repository: 스프링 데이터 접근 계층에서 사용, 스프링 데이터 접근 계층으로 인식, 데이터 계층의 예외를 스프링 예외로 변환
  • @Configuration: 스프링 설정 정보에서 사용, 스프링 설정 정보로 인식, 싱클톤 유지하도록 처리

 

필터

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

)
static class ComponentFilterAppConfig {

}

특정 애노테이션이 붙은 클래스를 스캔 목록에 포함하거나 제외할 수 있다.

@MyIncludeComponent
public class BeanA {
}
@MyExcludeComponent
public class BeanB {
}

BeanA는 스캔 목록에 포함되고 BeanB는 스캔 목록에서 제외되어 스프링 빈에는 BeanA만 등록된다.

 

중복 등록과 충돌

컴포넌트 스캔에서 같은 빈 이름을 등록하면 어떻게 될끼?

자동으로 스프링이 같은 이름의 빈을 등록하게 되면 충돌이 나면서 오류를 발생시킨다.

자동으로 등록되는 빈과 수동으로 등록되는 빈이 충돌하게 될 경우에는 개발자가 수동으로 등록한 빈이 스프링 빈에 등록된다.

(수동 빈이 자동 빈을 오버라이팅 한다)

하지만 알더라도 의도적으로 설정해서 사용하지는 말자! 잘못하면 잡기 어려운 버그가 만들어 질 수 있다.

스프링 부트에서는 수동 빈 등록과 자동 빈 등록이 충돌나면 일부러 오류가 발생하도록 기본 값을 바꾸었다.