김영한님의 스프링 핵심 원리 - 기본편 강의
섹션6. 컴포넌트 스캔 강의 요약입니다.

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

설정 정보가 없어도 자동으로 스프링 빈을 등록하는 @ComponentScan
@Component가 붙은 클래스를 스캔에서 스프링 빈으로 등록한다.

의존 관계도 자동으로 주입하는 @Autowired 기능도 제공. 생성자에서 여러 의존관계를 한 번에 주입할 수 있다.
스프링 컨테이너가 자동으로 해당 스프링 빈을 찾아서 주입한다. 기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다. getBean(MemberRepository.class)

탐색 위치와 기본 스캔 대상

basePackages, basicPackageClasses 사용하여 탐색 시작할 package 위치 지정 가능하다.
지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다.
따로 지정하지 않고 설정 정보 클래스의 위치를 프로젝트 최상단에 두자. 스프링 부트도 이 방법을 기본으로 제공한다.


@Configuration
@ComponentScan(
      basePackages = "hello.core.member", //  어디서부터 찾을지 시작위치 지정할 수 있음
      basePackageClasses = AutoAppConfig.class, // 지정한 클래스의 package 위치를 탐색 시작함
       excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class))
public class AutoAppConfig {

}

@SpringBootApplication 안에 ComponentScan이 들어있다. 스프링을 쓰면 따로 해줄 필요가 없다.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
        @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

컴포넌트 스캔은 @Component 뿐만 아니라 밑의 Annotation들도 @Component와 같은 역할을 하면서 추가 역할을 제공한다.

  • @Controller: 스프링 MVC 컨트롤러로 인식
  • @Repository: 스프링 데이터 접근 계층으로 인식. 데이터 계층의 예외를 스프링 예외로 변환
  • @Configuration: 스프링 설정 정보로 인식. 스프링 빈이 싱글톤을 유지하도록 추가 처리를 한다.
  • @Service: 스프링 비즈니스 로직에서 사용. 특별한 처리를 하지 않는다. 대신 개발자들이 핵심 비즈니스 로직이 여기에 있겠구나 라고 비즈니스 계층을 인식하는데 도움이 된다.

중복 등록과 충돌

컴포넌트 스캔에서 같은 빈 이름을 등록했을 경우했을 경우 어떻게 될까?

  • 자동 빈 등록 vs 자동 빈 등록 -> ConflictingBeanDenitionException 발생
  • 수동 빈 등록 vs 자동 빈 등록
    • 수동 빈이 우선권을 가짐
    • 수동 빈이 자동 빈을 오버라이딩 해버린다.

자동 빈을 오버라이딩 한 log

23:47:57.103 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Overriding bean definition for bean 'memoryMemberRepository' with a different definition: replacing [Generic bean: class [hello.core.member.MemoryMemberRepository]; scope=singleton; abstract=false; lazyInit=null; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodNames=null; destroyMethodNames=null; defined in file [C:\Users\chlgp\Github\Spring-Basic\core\out\production\classes\hello\core\member\MemoryMemberRepository.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=null; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=autoAppConfig; factoryMethodName=memberRepository; initMethodNames=null; destroyMethodNames=[(inferred)]; defined in hello.core.AutoAppConfig]

BUT 현실은 개발자가 의도하기 보다는, 놓친 버그. 이렇게 되었을 경우 찾기 어려운 버그가 되어버려 최근 스프링부트는 오류가 발생하도록 기본 값을 바꾸었다. spring.main.allow-bean-definition-overriding=true 을 설정해두면 오버라이딩하여 Application이 실행되고, 기본 값으로 실행하면 오류가 발생하게 된다.

***************************
APPLICATION FAILED TO START
***************************

Description:

The bean 'memoryMemberRepository', defined in class path resource [hello/core/AutoAppConfig.class], could not be registered. A bean with that name has already been defined in file [C:\Users\chlgp\Github\Spring-Basic\core\out\production\classes\hello\core\member\MemoryMemberRepository.class] and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true


Process finished with exit code 1