앞에서 포스팅 한 글에서는 스프링빈 등록과 의존성 주입을 아래와 같이 설정 정보에 수동으로 넣어주었다.
AppConfig.class
@Configuration
public class AppConfig {
@Bean
public MemberService memberService(){
System.out.println("AppConfig.memberService");
return new MemberServiceImpl(memberRepository());
}
@Bean
public MemberRepository memberRepository() {
System.out.println("call AppConfig.memberRepository");
return new MemoryMemberRepository();
}
@Bean
public OrderService orderService(){
System.out.println("AppConfig.orderService");
return new OrderServiceImpl(memberRepository(), discountPolicy());
}
@Bean
public DiscountPolicy discountPolicy() {
return new FixDiscountPolicy();
}
}
현재는 등록해야 할 스프링 빈이 몇개 안되지만 만약 등록해야 할 빈이 수 십개가 넘어가면 너무 귀찮아질 것이다. 이런 문제를 해결할 수 있게 해주는 것이 컴포넌트 스캔이다. 컴포넌트 스캔을 사용하기 위해서는 위의 AppConfig와 같은 설정 정보에 @ComponentScan을 붙여주면된다.
@Configuration
@ComponentScan
public class AutoAppConfig {
}
그런데 AppConfig와는 다르게 스프링빈 등록 정보가 하나도 없다. 그런데 어떻게 빈을 등록하고 의존성 주입을 하게 되는 것일까?
바로 아래와 같이 등록할 클래스에 @Component를 붙여주면된다.
@Component
public class RateDiscountPolicy implements DiscountPolicy{
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if(member.getGrade() == Grade.VIP)
return price*discountPercent/100;
return 0;
}
}
그러면 컴포넌트 스캔이 시작되면서 @Componenet가 붙은 클래스들을 찾아 스프링빈으로 등록해준다. 등록된 스프링빈의 기본 이름은 @Component가 붙은 클래스명(ex:RateDiscountPolicy)으로 하되 맨 앞글자만 소문자로 바꾼다(ex:rateDiscountPolicy). 만약 빈이름을 직접 지정하고 싶으면 @Component("원하는이름")이런식으로 이름을 부여할 수 있다.
그런데 만약 빈이름에 중복이 일어나면 어떻게 될까? 컴포넌트스캔으로 등록된 빈이름이 겹치는 경우 바로 ConflictingBeanDefinitionException 예외가 발생한다. 그리고 수동으로 등록한 빈이름과 컴포넌트 스캔으로 등록된 빈이름이 같다면 수동으로 입력한 빈이 우선권을 가지게 된다. 하지만 이런 애매한 상황은 만들지 않는 것이 좋다!
🔍컴포넌트 스캔 대상(아래의 어노테이션 안에는 @Component가 있음)
@Component : 컴포넌트 스캔에서 사용
@Controller : 스프링 MVC 컨트롤러에서 사용
@Service : 스프링 비즈니스 로직에서 사용
@Repository : 스프링 데이터 접근 계층에서 사용
@Configuration : 스프링 설정 정보에서 사용
그리고 위에서 설명하지 않았지만 @SpringBootApplication안에는 @ComponentScan이 들어있어 스프링 애플리케이션을 시작하면 컴포넌트 스캔이 자동으로 된다.
@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;
}
@Override
public Order createOrder(Long memberId, String itemName, int itemPrice) {
Member member = memberRepository.findById(memberId);
int discountPrice = discountPolicy.discount(member, itemPrice);
return new Order(memberId, itemName, itemPrice, discountPrice);
}
위 코드처럼 생성자에 @AutoWired를 추가해주면 파라미터에 해당하는 타입의 빈을 조회하여 의존성을 주입해준다. 더 놀라운 것은 만약 생성자가 하나밖에 없다면 @AutoWired를 생략해도 의존성 주입을 시켜준다. 생성자에 주입하는 방식 말고도 수정자 주입(setter), 필드 주입, 일반 메서드 주입이 있다. 하지만 생성자 주입을 사용하는 것이 가장 바람직한데 그 이유는 아래와 같다.
1. 불변
2. 누락
3. final 키워드
@AutoWired는 타입으로 조회한다. 예를들어 DiscountPolicy의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy가 스프링빈으로 등록되어있다고 가정하자.
@AutoWired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
위 코드에서는 어떤 타입을 받아와야할까? 저 코드는 실행하면 NoUniqueBeanDefinitionException예외가 터진다. 그렇다고 하위타입으로 지정하면 DIP가 깨지게된다. 그럴 때는 다음과 같은 방법이 있다.
1. @AutoWired 필드명 매칭
@AutoWired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
위 코드와 같이 DiscountPolicy ->rateDiscountPolicy로 변경하여 주입을 받고자 하는 빈의 이름으로 매칭시킬 수 있다.
2. @Qualifer
RateDiscountPolicy
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
OrderServiceImpl
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
위 코드와 같이 이름이 중복되는 스프링빈에 @Qualifier("원하는 이름")을 넣어주면된다. 스프링 빈이름이 바뀌는게 아니라 구별하기 위해서만 쓰이는 것이다.
3. @Primary
RateDiscountPolicy
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
위 코드처럼 @Primary를 추가하게 되면 해당 스프링빈이 주입된다. 가장 추천하는 방법이다.