기존 AppConfig 파일에서는 스프링 빈을 직접 등록 하였다
@Bean 어노테이션을 사용하였으며, 생성자 호출 시 객체를 리턴하여 의존성을 주입하였다.
@Configuration
public class AppConfig {
@Bean
public MemberService memberService() {
System.out.println("call AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemoryMemberRepository memberRepository()
{
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public static DiscountPolicy discountPolicy() {
return new RateDiscountPolicy();
}
}
그러나, 직접 등록하는 방법은 비효율적이고 귀찮다.
스프링은 @ComponentScan을 통해 자동으로 스프링 빈을 등록할 수 있으며, @Autowired를 통해 의존성을 주입한다.
package hello.core;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
@Configuration
@ComponentScan(
basePackages = "hello.core.member",
excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION, classes =
Configuration.class))
public class AutoAppConfig {
}
다음과 같이 AppConfig 클래스에 @ComponentScan 어노테이션을 달아주면,
자동으로 @Component 어노테이션이 붙은 클래스를 스프링 빈으로 등록한다.
@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;
}
OrderServiceImpl 클래스는 @Component 어노테이션이 붙었기에, orderServiceImpl이라는 이름의 스프링 빈이 등록되는 것이다.
(추가로, 스프링빈의 이름은 @Component가 붙은 클래스 이름을 가져온다. 단 첫글자는 소문자 형태이다.)
해당 클래스 생성자에는 memberRepository와 discountPolicy 의존성 주입이 필요하다.
기존 AppConfig의 경우,
@Bean
public OrderService orderService(){
System.out.println("call AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
를 통해 의존성 주입이 이루어졌지만, @ComponentScan을 사용할 경우 직접 주입할 수 없기에 @Autowired 어노테이션을 사용한다. @Autowired를 사용하면 스프링 컨테이너에 등록된 스프링 빈에서 자동으로 memoryMemberRepository와 rateDiscountPolicy을 찾아 주입하는 것이다.
기본 조회 전략은 타입이 같은 빈을 찾아서 주입한다. ac.getBean(MemberRepository.class)를 하는 것과 같다.
@Component 어노테이션 뿐만 아니라, 자동으로 스프링빈으로 등록되는 경우가 있다. 예를 들어 @Configuration, @Service 어노테이션의 경우 구체 코드를 보면 @Component 어노테이션을 포함하고 있다. 때문에 해당 어노테이션을 가진 클래스는 스프링빈으로 등록된다.
@Component 어노테이션을 찾아 전체를 다 뒤지기 때문에, 탐색 위치가 중요하다. 기본값으로 @ComponentScan 이 붙은 설정 정보 클래스의 패키지가 시작 위치가 된다. 때문에 설정 정보 클래스의 위치를 프로젝트 최상단으로 두는 것이 좋다. 여기서는 AutoAppConfig를 최상단에 두어, 내가 작성한 모든 파일을 탐색하도록 하였다.
컴포넌트 스캔이 이루어지는 대상을 제외할 수도 있다.
excludeFilters 옵션으로 어노테이션, 타입을 지정하여 해당 클래스는 등록하지 않는 것이다.
지금까지 @Bean을 통한 직접 빈 등록, @ComponentScan을 통한 자동 빈 등록 방법을 알아보았다. 그렇다면 빈 등록이 중복되는 경우는 두 가지 상황을 떠올릴 수 있다.
자동 빈 등록 vs 자동 빈 등록
이 때는 ConflictingBeanDefinitionException 에러가 발생한다.
수동 빈 등록 vs 자동 빈 등록
Spring에서는 수동 빈이 자동 빈을 덮어 쓴다 (오버라이딩). 때문에 에러 메시지가 발생하지 않는다. 오버라이딩을 의도하지 않은 경우에는 오류를 찾기 어려운 경우가 많다.
이에 대해 최신 스프링 부트는 오버라이딩에 있어 '불가능'을 기본 값으로 가진다. 때문에 수동 빈 등록과 자동 빈 등록이 중복된 경우,
Consider renaming one of the beans or enabling overriding by setting
spring.main.allow-bean-definition-overriding=true
이러한 형태의 에러 메시지를 남기어, 만약 오버라이딩을 의도한 경우에는 해당 옵션을 TRUE로 바꿀 것을 알려준다.