1. 컴포넌트 스캔

 이전에 스프링 빈을 등록할 때는, 자바 코드의 @Bean이나 XML의 <bean> 등을 통해 설정 정보에 등록할 스프링 빈들을 나열해야했다. 만약 이렇게 등록해야할 스프링 빈의 갯수가 많아진다면 매우 번거로운 작업이 될 것이며, 누락되는 일도 생길 것이다. 그래서 스프링은 별도의 설정 정보 없이 자동으로 스프링 빈을 등록해주는 컴포넌트 스캔이라는 기능을 제공한다. 컴포넌트 스캔은 @Component 어노테이션이 붙은 클래스를 찾아 스프링 컨테이너에 빈으로 등록해주는 기능을 말한다.

 이때, 설정 정보에서 일일히 해주던 의존 관계 주입은, @Autowired라는 어노테이션을 통해 스프링에 위임하게 된다. 스프링 컨테이너에 해당 타입의 빈이 존재한다면, 이를 주입해주는 것이다.

@Component
public class MemberServiceImpl implements MemberService {

    private final MemberRepository memberRepository;

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

    @Override
    public void join(final Member member) {
        memberRepository.save(member);
    }
}
@Configuration
@ComponentScan
public class AutoAppConfig {
}
class AutoAppConfigTest {
    @Test
    void basicScan() {
        final AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

        final MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

 

2. 컴포넌트 스캔 탐색 위치

 컴포넌트 스캔을 할 패키지를 아래와 같이 지정할 수 있다. 특별히 지정하지 않는다면, 디폴트는 @ComponentScan 어노테이션이 붙은 설정 정보 클래스가 있는 패키지가 탐색할 패키지의 시작 위치가 된다.

 일반적으로는 특별히 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이 좋다. 프로젝트의 메인 설정 정보는 프로젝트를 대표하는 정보이기 때문이다. 참고로 스프링 이니셜라이저를 통해 스프링 부트 프로젝트를 생성하면, @SpringBootApplication이 붙은 클래스가 프로젝트 최상단에 생성되는데, 이 어노테이션 내에는 @ComponentScan이 포함되어 있다.

@Configuration
@ComponentScan(basePackages = "hello.core")
public class AutoAppConfig {
}

 

3. 컴포넌트 스캔 기본 대상

  • @Component
  • @Controller : 스프링 MVC 컨트롤러에서 사용
  • @Service : 스프링 서비스 레이어에서 사용
  • @Repository : 스프링 데이터 접근 계층에서 사용
  • @Configuration : 스프링 설정 정보에서 사용

@Component를 제외한 나머지 어노테이션들의 소스 코드를 보면, @Component 어노테이션을 포함하고 있는 것을 확인할 수 있다.

 

* 참고 : 사실 어노테이션에 상속 관계라는 것은 없다. 따라서 어떤 어노테이션이 다른 어노테이션을 포함한다고 해서 그 어노테이션을 인식할 수 있는 것은, 자바 언어가 지원하는 기능은 아니다. 이것은 스프링이 지원하는 기능이다.

 

 위에서 컴포넌트 스캔의 대상이 되었던 어노테이션들은 이 용도 외에도 다른 스프링의 부가 기능을 수행한다.

  • @Controller : 스프링 MVC 컨트롤러로 인식
  • @Repository : 스프링 데이터 접근 계층으로 인식. 데이터 계층에서 발생하는 예외를 스프링 예외로 변환해준다. 왜냐하면, 만약 사용하는 데이터 구현체가 변경되었을 때 예외 종류까지 변경된다면, 이전에 처리해놓았던 예외 처리 또한 모두 변경되어야하기 때문이다. 이를 스프링 예외로 한 번 감싸줌으로써, 구현체에 종속적이지 않을 수 있도록 해준다. 
  • @Configuration : 스프링 설정 정보로 인식. 스프링 빈이 싱글톤으로 유지될 수 있도록 추가적인 처리를 한다.
  • @Service : 사실 특별한 처리를 하는 것은 없다. 다만 개발자들이 여기가 비즈니스 계층이라는 것을 인지하는데 도움을 준다.

 

4. 중복 등록과 충돌

 이름이 같은 빈들이 등록되면 어떻게 될까? 다음 두 가지 상황이 있을 수 있다.

1) 컴포넌트 스캔에 의한 등록 vs 컴포넌트 스캔에 의한 등록 : ConflictingBeanDefinitionException 예외가 발생한다.

@Component("discountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
}

@Component("discountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {
}

 

2) @Bean에 의한 등록 vs 컴포넌트 스캔에 의한 등록 : 이 경우에는 @Bean에 의한 등록이 우선권을 가진다. 즉, @Bean에 의한 빈이 컴포넌트 스캔에 의한 빈을 오버라이딩한다. 물론 개발자가 이를 의도적으로 의도했다면 상관이 없지만, 현실은 여러 설정들이 꼬여서 이런 결과가 만들어지는 경우가 대부분이다. 이럴 경우 정말 잡기 어려운 버그가 만들어진다. 그래서 최근 스프링 부트에서는 이 경우에도 빈 등록에 충돌이 일어나면 오류가 발생하도록 기본 값을 변경하였다. 만약 오버라이딩을 하도록 변경하고 싶다면, spring.main.allow-bean-definition-overriding=true 로 설정하면 된다.

@Configuration
@ComponentScan
public class AutoAppConfig {
    @Bean(name = "memoryMemberRepository")
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
    }
}

class AutoAppConfigTest {
    @Test
    void basicScan() {
        final AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class);

        final MemberService memberService = ac.getBean(MemberService.class);
        assertThat(memberService).isInstanceOf(MemberService.class);
    }
}

 

+ Recent posts