Programming-[Backend]/Spring

[스프링 기초] 12. 컴포넌트 스캔(Component Scan)

컴퓨터 탐험가 찰리 2021. 7. 4. 15:39
728x90
반응형

1. 컴포넌트 스캔 개념

@ComponentScan, @Component

이때까지 컨테이너의 설정 정보 작성은 적은 수의 스프링 빈으로 작성하였다. 그러나 실무에서는 수 십, 수 백개의 빈들을 등록하고, 관리해야되는 문제가 발생한다. 스프링은 이런 문제를 해결하기 위해 설정 정보가 없어도 자동으로 스프링 빈을 등록하는 컴포넌트 스캔 기능을 제공한다.

@ComponentScan은 말그대로 @Component 애노테이션이 붙은 클래스들을 스캔한다. @Configuration 내부에 있는 @Component도 스캔하므로, @Configuration은 filter로 제외한다(예제 코드상 다른 위치에 @Configuration이 붙어있는 클래스가 있기 때문). 이때 스프링 빈의 기본이름은 해당 클래스명을 맨 앞글자만 소문자로 바꾸어서 지정하도록 되어있다(PlanServiceImpl.class -> planServiceImpl.class). 그리고 혹시라도 직접 지정이 필요하다면, @Component("XxxRepository")와 같은 방식으로 가능하다.

@Configuration과 @ComponentScan을 적용한 설정 정보 파일. 기존에 비해 클래스 내부에 @Bean들을 설정하지 않는 것을 볼 수 있다.
@Configuration 코드 내부. @Component 어노테이션이 포함되어 있다.



@ComponentScan의 대상

그 외에 @Controller, @Service, @Repository 애노테이션이 붙은 클래스도 스캔해준다. 각 애노테이션의 코드 파일에 들어가보면 @Component 애노테이션을 포함하고 있는 것을 볼 수 있다.

의존관계 설정

각 구현체가 스캔될 수 있도록 하기 위해서 @Component를 붙여주도록 한다. 그런데, 각 구현체에서 정의된 의존관계는 어떻게 스캔될까? 다시 말해서 아래 사진의 CreatureServiceImpl 클래스에서 CreatureRepository에 대한 의존관계는 어떻게 처리되는 것일까? 원래는 AppConfig.class에서 빈들간의 의존관계를 주입해줬는데, 이렇게 자동으로 스캔되게 해버리면 의존관계 주입을 할 방법이 없다. 이것을 자동으로 처리해주는 것이 @Autowired 이다. 각 구현체의 생성자 위에 붙여주도록 한다. 설정 정보 파일에서 의존관계를 설정해주던 것을, 각 컴포넌트 파일의 생성자에서 의존관계를 설정해주는 개념이라고 생각하면 된다. 즉, 설정 정보 파일 내에서 작성할 내용이 없어지기 때문에 클래스 파일 안에서 의존관계도 해결하는 것이다.

CreatureServiceImpl 구현체에 @Autowired 적용
PlanServiceImpl에 @Autowired로 의존관계 설정



@Autowired는 빈 타입을 기반으로하여 의존관계를 주입한다.

빈의 이름이 아니라 타입을 기반으로 하므로, 부모 타입으로 조회하는 경우 여러 개의 빈이 조회될 수 있으므로 따로 처리가 필요하다. 관련된 내용은 14. 여러 개의 빈이 있을 때 자동주입하기 글에서 학습할 것이다.

테스트해보면 정상적으로 작동하고, ClassPathBeanDefinitionScanner -, Singleton -, AutoWiring - 등의 정보가 콘솔 창에 출력되는 것을 확인할 수 있다.


2. ComponentScan의 속성 : basePackages, filter

@ComponentScan의 basePackages = {" "} 속성

basePackages 속성을 이용해서 컴포넌트 스캔이 시작되는 패키지 위치를 지정할 수 있다. 시작 위치로부터 하위 패키지만 스캔하여 빈들을 등록하게 된다. " " 로 구분하여 여러 패키지를 설정할 수 도 있다. 만약 basePackages를 지정하지 않으면, @ComponentScan을 지정한 파일이 속해있는 패키지를 모두 스캔하게 된다. 따라서 @ComponentScan 애노테이션이 붙은 설정 정보 파일은 보통 프로젝트 패키지 바로 밑, 즉 프로젝트 최상단에 둔다. 만약 아래와 같이 패키지가 구성되어 있다면,

  • com.example
  • com.example.service
  • com.example.repository...

com.example 패키지 아래에 설정 정보 파일을 두는 것이다.


필터

필터는 두 종류로 설정할 수 있다.

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

다음과 같은 형태로 어노테이션 파일을 만든다.

BeanA에는 MyIncludeComponent 어노테이션을 달아주고, BeanB에는 MyExcludeComponent 어노테이션을 달아준다.


@ComponentScan에 includeFilers, excludeFilters 속성을 어노테이션 옵션으로 지정하고, 테스트 해보면 beanA는 조회가 되고, beanB는 조회가 되지 않아서 NoSuchBeanDefinitionException이 발생한다.


필터 옵션

  • ANNOTATION : 기본값이며 애노테이션을 인식해서 동작한다.

ex) com.example.XxxAnnotation

  • ASSIGNABLE_TYPE : 지정한 타입과 자식 타입을 인식해서 동작한다.

ex) com.example.XxxClass

  • ASPECTJ : AspectJ 패턴을 사용한다

ex) com.example..*Service+

  • REGEX : 정규표현식

ex) com.\example\.Default.*

  • CUSTOM : 'TypeFilter" 라는 인터페이스를 구현해서 처리한다.

ex) com.example.MyTypeFilter



3. 중복 등록과 충돌

자동 등록 vs 자동 등록

@ComponentScan시, @Component로 지정한 빈들이 자동 등록되는데, 이름이 같은 빈들이 있다면 ConflictingBeanDefinitionException 예외가 발생한다. 이럴 때는 이름을 다르게 지정하여 해결해야 한다.
ex) 만약 PlanService도 "service"라는 이름을 갖고, CreatureService도 "service"라는 이름을 가진채로 스캔이 되면 오류가 발생한다.



자동 등록 vs 수동 등록


자동 등록이 된 상태에서 @Bean으로 설정 정보 파일에 수동 등록을 하면 어떤게 우선적으로 적용될까? 이때는 수동이 우선권을 가져서 오버라이딩이 된다. 그러나 이런 경우는 의도했다기보다는 실수인 경우가 많다. 원래 스프링에서는 이 경우 경고 알림을 주었으나, 최근 스프링부트에서는 에러를 발생시킨다. application.properties에 예외설정을 할 수 있지만, 이런 명확하지 않은 경우는 만들지 않는 것이 좋다.

수동으로 creatureRepository를 @Bean으로 등록하고 테스트 해본다. CreatureRepository는 @Component 애노테이션에 의해서 자동 스캔도 동시에 되는 상황이다.


CreatureRepository 빈을 조회하면,

에러가 발생한다. 자동으로 스캔된 creatureRepository 빈을 조회하는 것인지, 수동으로 등록한 creatureRepositoryAnimals를 조회하는 것인지 알 수 없기 때문이다.


참조

1. 인프런_스프링 핵심 원리 기본편_김영한 님 강의
www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard

728x90
반응형