본문 바로가기

spring

@ComponentScan

@ComponentScan

  •  에플리케이션의 컴포넌트들을 찾아 빈으로 등록한다.
  • 컴포넌트란, @Component 에너테이션이 붙은 클래스.
  • @Configuration 할 클래스에 붙여준다.
@ComponentScan
@Configuration
public class AutoConfig {
}

 

 

이 컴포넌트 스캔이 있기 전에는 @Configuration 으로 지정한 클래스에 @Bean 에너테이션을 이용하여, 빈 등록과 의존성 주입을 했다.

하지만 @ComponentScan 을 이용한 빈 등록을 처리하면, 의존성 주입은 어디서 할까?

 

--> @Autowired 를 이용하여 @Component 에너테이션이 붙은 클래스에서 자동 주입을 한다.

 

수동 빈 설정과 같이 ApplicationContext 생성시 해당 설정 클래스를 넣어주면 빈을 꺼낼 수 있다.

 

@Test
void getBean() {
    ApplicationContext ac = new AnnotationConfigApplicationContext(AutoConfig.class);
    
    ac.getBean(~);
}

 

 

 

 

 

컴포넌트 스캔과 의존관계 주입 과정

1. @ComponentScan

  • @Component 가 붙은 모든 클래스를 스프링 빈으로 등록한다.
  • 스프링 빈의 기본 이름은 클래스명을 사용한다. 단 가장 앞글자를 소문자로 치환한다.
    • 생각해보자. 수동 방식의 빈 주입이 원래 존재했고, 이는 메서드이름을 이름으로 했었다.
    • 메서드 이름은 보통 lowCamelCase 를 관례상 이용했다.
    • 하지만 이 자동주입방식은 클래스 이름을 사용하는데, 클래스 이름은 upperCamelCase 가 관례다.
    • 이 두 간극을 매꾸기 위해 앞글자를 소문자로 하지 않았을까 하는 추측을 해본다.
  • 빈 이름을 직접 지정하고 싶다면, @Component("원하는이름") 이 가능하다.

2. @Autowired

  • 생성자 주입 : 생성자에 @Autowired 를 붙이면 자동으로 스프링 빈을 찾아 주입한다.
  • 기본 조회 전략 : 생성자 매개변수의 타입이 같은 빈을 찾는다.

 

스캔 위치 설정

@ComponentScan 에너테이션에는 basePackage 옵션을 줄 수 있다.

@ComponentScan(
    basePackages = "com.example"
)

위와 같이 하면, 컴포넌트를 스캔하는 작업을 com.example 패키지의 하위는 모두 스캔 대상에 포함하는 것이다.

{} 를 이용하여 복수로 등록할 수 있다.

 

권장

이 옵션을 지정하지 않으면 기본 디폴트는 해당 @ComponentScan 에너테이션이 붙은 패키지의 하위들이다.

따라서 굳이 basePackages 옵션을 지정하지 않고, 해당 클래스를 에플리케이션 최상위에 두면 모든 클래스들을 스캔할 수 있다.

 

Springboot

스프링부트에서는 프로젝트를 만들면 @SpringBootApplication 이라는 에너테이션이 붙은 메인함수를 담은 클래스가 프로젝트 소스코드 최상단에 자동으로 생성된다.

@SpringBootApplication 에는 @ComponentScan 에너테이션이 포함되어 있다. 따라서 우리는 스프링부트를 사용하면 따로 @ComponentScan 을 할 필요가 없는 것이다. 그리고 최상단에 이 클래스가 위치하기에, 에플리케이션의 모든 클래스가 스캔의 대상이 된다.

 

 

스캔 대상

아래의 에너테이션이 붙은 클래스들이 빈 스캔 대상이다.

  • @Component : 컴포넌트 스캔에 사용
  • @Controller : 스프링 MVC 의 컨트롤러에 사용
  • @Service : 특별한 처리는 없고 서비스라는 것을 알려준다.
  • @Repository
    • 이 에너테이션은 빈 스캔 대상을 지정하는 역할 외에도 아래와 같은 역할을 한다.
    • DB 를 관리하는 대상은 엄청 많다. 하지만 DB 마다 발생할 수 있는 예외들은 구현체별로 다르다.
    • 이 예외들은 서비스단까지 올라와서 처리하게 되는데, 이 예외들이 다 다르면 DB 구현체를 바꿔낄때마다 예외처리를 매번 해줘야할 것이다.
    • 스프링의 @Repository 에너테이션은 이 DB 에서 발생할 수 있는 예외들을 추상화하여 스프링 예외로 변환해준다.
    • 우리는 일관적인 예외처리를 할 수 있게 된다.
  • @Configuration : 스프링 설정 정보에 사용. 스프링 빈이 싱글톤을 유지하도록 해준다. (CGLIB 를 이용한 바이트코드 조작)

 

지금까지 @Controller, @Service, @Repository 들이 @Component 를 상속한다 라고 알고 있었다.

하지만 에너테이션에는 따로 상속관계는 없다. 자바단이 아닌 스프링단에서 @Controller, @Service, @Repository 를 스프링 빈으로 인식하는 기능을 제공하는것이다.

 

 

 

필터

필터는 @ComponetScan 에 줄 수 있는 옵션이다. 컴포넌트 스캔의 대상을 명시적으로 지정하거나 제외할 수 있다.

  • includeFilter : 기본 대상 패키지 외에 스캔 대상으로 추가 지정한다.
  • excludeFilter : 기본 대상 패키지 중에 스캔 대상의 예외로 지정한다.
@ComponentScan(
    includeFilters = @Filter(type = FilterType.ANNOTATION, classes = 에너테이션.class),
    excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = 에너테이션.class)
)

 

위와 같은 옵션을 줄 수 있으며, classes 에 지정한 에너테이션이 붙은 클래스는 추가하거나 제외할 수 있는 것이다.

 

FilterType 열거형에는 여러가지 옵션이 존재하며, 위의 FilterType.ANNOTATION 는 특정 에너테이션이 붙었는가 를 따져서 처리하겠다는 뜻이다.

 

타입과 자식 타입을 검사하거나, 정규식으로 검사하거나의 다른 옵션들이 존재한다.

 

 

 

 

 

충돌

수동 빈 등록, 자동 빈 등록이 생기며 충돌이 발생할 수 있어졌다.

충돌이란, 스프링 빈 컨테이너에 같은 이름의 빈이 두개가 있어서 발생하는 에러이다.

수동 빈 등록에서 beanA 라는 이름으로 등록했는데, 자동 빈 등록에서 또 beanA 를 등록하려는 그런 상황이다.

 

1. 수동 빈 등록 vs 수동 빈 등록

  • 이런건 없다.
  • 똑같은 메서드를 같은 클래스에서 지정할 수 없다.

2. 자동 빈 등록 vs 자동 빈 등록

  • @Component("~") 식으로 등록하면 빈 이름을 지정할 수 있다고 했다.
  • ConflictingBeanDefinitionException 예외가 발생한다.
  • 그러니까 그냥 기본 옵션으로 지정해주는 이름을 쓰자.

3. 수동 빈 등록 vs 자동 빈 등록

  • 협업시 가장 발생 할 확률이 높다.
  • 이 경우 수동 빈 등록이 우선권을 가진다.
    • 수동 빈 등록이 자동 빈을 오버라이딩한다.
    • 이는 스프링의 기본 정책이다.
  • 하지만 이렇게 오버라이딩해버리는 것은 로그에서 볼 수 있지만, 에러가 나지 않는다.
  • 따라서 개발자가 자동 빈 등록한 빈을 사용하기로 의도했다면, 왜 의도한대로 동작하지 않는지 알기가 어렵다.
  • 잡기 어려운 애매한 버그이다.
  • 따라서 최근 스프링 부트(스프링아님) 에서는 이런 충돌이 발생하면 에러를 뱉고 프로그램을 멈춰버린다.