컴포넌트 스캔

naeganugu·2022년 7월 18일
0

스프링 마스터🌱

목록 보기
11/19

지금까지는 AppConfig 파일에 직접 @Bean을 통해서 스프링 빈을 등록했었다. 지금부터는 자동으로 스프링 빈을 등록해주는 컴포넌트 스캔에 대해 알아봅시다.

1) @ComponentScan

컴포넌트 스캔을 사용하려면 @ComponentScan 어노테이션을 붙여준다. 컴포넌트 스캔은 말 그대로 @Component 어노테이션이 붙은 클래스들을 스프링 빈으로 동록해준다.

@Component: 컴포넌트. 스프링 빈으로 등록하고 싶은 클래스들.
@ComponentScan: @Component 어노테이션이 붙은 클래스들을 스캔해서 스프링 빈으로 등록.

@Component
public class MemoryMemberRepository implements MemberRepository {}
    
...

@Component
public class RateDiscountPolicy implements DiscountPolicy {}

위 코드처럼 @Component 어노테이션을 달아주면, @ComponentScan이 MemoryMemberRepository와 RateDiscountPolicy를 스프링 빈으로 등록해준다.

@Configuration
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class) // @Configuration 붙은 거 제외, 실무에서는 이렇게 안하는데 기존 예제코드와 겹칠까봐 하는 것.
)
public class AutoAppConfig {

}

이제 설정 정보에는 @ComponentScan 어노테이션을 붙여주면 된다. 또 직접 AppConfig에서 @Bean으로 클래스를 등록해주지 않아도 스프링 빈으로 등록된다.

2) @Autowired

스프링 빈으로 등록하는거는 설정 정보에 @ComponentScan 어노테이션을 붙여주면, @Component 어노테이션이 붙은 클래스들을 스프링 빈으로 자동 등록해준다. 그렇다면 의존관계 주입은 어떻게 할까?

기존에는 @Bean을 써서 직접 스프링 빈으로 등록하고, 생성자를 통해서 직접 AppConfig에서 의존관계 주입을 해줬다.

이제는 @Autowired를 사용해서 의존성을 주입한다.

@Autowired: 자동 의존관계 주입. 스프링 컨테이너가 이 어노테이션이 붙어있다면 스프링 빈을 찾아서 주입.

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository; // 인터페이스만 존재

    @Autowired // 자동 의존관계 주입, ac.getBean(MemberRepository.class)
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
@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를 사용해서 생성자를 통해 의존관계를 주입 받는다.

ApplicationContext ac = new AnnotationConfigApplicationContext(ComponentFilterAppConfig.class);

AnnotationConfigApplicationContext 부분은 동일하다. 인자로 @ComponentScan이 붙은 설정 정보 클래스를 넣어주면 된다.

3) 컴포넌트 스캔과 자동 의존관계 주입이 일어나는 과정

우선 컴포넌트 스캔은 @ComponentScan이 @Component 어노테이션이 붙은 클래스를 모두 스프링 빈으로 등록해준다. 이때 빈의 이름은 맨 앞자리만 소문자로 바뀐 클래스명을 사용한다.

@Component
public class MemberServiceImpl implements MemberService{

    private final MemberRepository memberRepository; // 인터페이스만 존재

    @Autowired // 자동 의존관계 주입, ac.getBean(MemberRepository.class)
    public MemberServiceImpl(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

이 코드에서 MemberServiceImpl이 스프링 빈으로 등록될 때 빈 이름은 memberServiceImpl이 된다. ac.getBean()을 사용해서 이름으로 빈 조회할 때 이 부분을 기억하면 좋다! 만약 내가 따로 이름을 붙여주고 싶다면, @Component("내가 붙여주고 싶은 이름")으로 어노테이션을 적어주면 된다.

자동 의존관계 주입은 @Autowired가 붙어있다면, 스프링 컨테이너가 스프링 빈을 찾아서 주입해준다. 이때 타입이 같은 빈을 넣어준다.

ac.getBean(MemberRepository.class)

위 코드와 동일한 것.

4) 컴포넌트 스캔 대상

그럼 컴포넌트 스캔을 할 때 어디를 스캔할까? 모든 자바 클래스를 다 스캔하면 시간이 오래 걸린다.

@ComponentScan(
        basePackages = "hello.core" // scan 범위 정하기, 요즘에는 주로 생략
)

이러면 hello.core 포함 하위 패키지를 모두 스캔한다.

package hello.core; // basePackages 정하지 않으면 여기부터 하위를 ComponentScan

@Configuration
@ComponentScan(
        excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class) // @Configuration 붙은 거 제외, 실무에서는 이렇게 안하는데 기존 예제코드와 겹칠까봐 하는 것.
)
public class AutoAppConfig {

}

위 코드처럼 스캔 시작 위치를 지정하지 않는다면, @ComponentScan 어노테이션이 붙은 설정 정보 클래스(AutoAppConfig)가 들어있는 패키지가 시작 위치가 된다. 여기서는 AutoAppConfig를 포함하고 있는 hello.core 패키지가 시작 위치가 된다.

권장하는 방법은 굳이 패키지 위치를 지정하지 않고, 설정 정보 클래스의 위치를 프로젝트 최상단에 두는 것이다.

@SpringBootApplication
public class CoreApplication {
	public static void main(String[] args) {
		SpringApplication.run(CoreApplication.class, args);
	}
}

스프링부트도 이 방법을 기본으로 제공한다. @SpringBootApplication을 따라 들어가면 @ComponentScan 어노테이션이 있다. 따라서 프로젝트 최상단부터 스캔하게 되는 것.

프로젝트 구조가 위와 같다면
📌 hello.core가 프로젝트 시작 루트, 여기에 AppConfig 같은 설정 정보 클래스를 두고 @ComponentScan을 통해 굳이 시작점을 지정하지 않아도 프로젝트 최상단부터 스캔해 내려오도록 한다.

@Component 말고도 다른 것들도 스캔 대상이 된다.

  • @Component
  • @Controller
  • @Service
  • @Repository
  • @Configuration

위 어노테이션들을 따라 들어가면 @Component를 포함하고 있다. 그래서 스캔이 되는 것! @Component 외의 것들은 컴포넌트 스캔 말고도 다른 부가 기능이 추가된 거라고 생각하면 된다.

5) 필터

includeFilters: 컴포넌트 스캔 대상을 추가로 지정
excludeFilters: 컴포넌트 스캔에서 제외할 대상 지정

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

@MyIncludeComponent 어노테이션이 붙으면 컴포넌트 스캔 대상이 되고, @MyExcludeComponent 어노테이션이 붙어있으면 컴포넌트 스캔 대상에서 빠진다는 코드다.

FilterType 옵션은 5개가 있다.

  • ANNOTATION
  • ASSIGNABLE_TYPE
  • ASPECTJ
  • REGEX
  • CUSTOM

includeFilters나 excludeFilters는 그렇게 쓸 일이 많지 않다고 한다.

6) 스프링 빈 등록 시 충돌

컴포넌트 스캔 시, 빈을 등록할 때 이름은 클래스 이름을 참고해서 자동으로 등록되거나 수동으로 등록되는 경우가 있다.

만약 자동 등록으로 등록된 빈 두 개가 이름이 같아서 충돌한다면 스프링은 ConflictingBeanDefinitionException 예외를 발생시킨다.

만약 수동으로 등록한 빈과 자동으로 등록된 빈이 충돌난다면, 수동 빈 등록이 우선권을 가지게 된다. 자동으로 등록은 @Component 어노테이션을 붙여준 경우고, 수동으로 등록은 @Bean으로 등록해준 경우다.

@Component
public class MemoryMemberRepository implements MemberRepository {}

위 코드가 자동 등록이고

@Configuration
@ComponentScan(
          excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class)
)
public class AutoAppConfig {
      @Bean(name = "memoryMemberRepository")
      public MemberRepository memberRepository() {
          return new MemoryMemberRepository();
      }
}

위 코드가 수동 등록이다.

이때 수동 빈이 자동 빈을 오버라이딩 해버린다. 덮어쓴다.

📌 그렇지만 최근 스프링 부트에서는 수동 빈이 오버라이딩 하는 것이 아니라, 오류가 나도록 기본 값을 설정해둔다.



[출처]

스프링 핵심 원리-기본편

profile
seungseung-zanggu

0개의 댓글