앞서 우리는 @Autowired
는 타입으로 조회하는 것을 배웠다.
그래서 마치 @Autowired는 ac.getBean() 함수처럼 동작한다고 이해한 바가 있다.
하지만 역시 타입으로 조회할 땐 하위 타입의 빈이 2개이상이 존재할 경우에는 문제가 발생한다.
그럼 DisountPolicy의 하위타입인 FixDiscountPolicy
와 RateDiscountPolicy
를 스프링 빈으로 선언해보자.
그러면 아래와 같이 NoUniqueBeanDefinitionException
에러가 터질 것이다.
그렇다면 이렇게 생각할 수 있다.
-> 아~니 그냥 더 하위타입인 FixDiscountPolicy
나 RateDiscountPolicy
로 주입하면 되는거 아님?
-> 어리석은 소리! 그러게 되면 DIP를 위반하고 유연성이 더 떨어진다. 왜?
-> 하위타입으로 고정되어버리니까 역할과 구현에서 DIP를 위반하기 때문이다.
항상 역할(인터페이스)에 의존해야지 구체적인것(~~impl)에 의존하면 안 된다고 했다.
그럼 어떻게 해결함? -> 물론 수동으로 하면 되긴 하지만 자동 의존관계 주입으로도 해결할 수 있는 방법이 3가지 정도 존재한다.
@Autowired
는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.// 기존 코드
@Autowired
private DiscountPolicy discountPolicy
// 필드 명을 빈으로 이름 변경한 코드
@Autowired
private DiscountPolicy reateDiscountPolicy
spring은 getBean을 하면 하위 자식 타입까지 모두 조회한다고 하였는데 이때 1차로 타입 매칭이 진행되고 타입 매칭 결과가 2개 이상일 경우 필드 명, 혹은 파라미터 명으로 빈 이름을 매칭해준다.
@Qualifier
는 추가 구분자를 붙여주는 방식이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.// RateDiscountPolicy.class
@Component
@Qualifier("rateDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{...}
// FixDiscountPolicy.class
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{...}
...
pricate final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(@Qualifier("rateDiscountPolicy")DiscountPolicy discountPolicy){
this.discountPolicy = discountPolicy
}
...
// RateDiscountPolicy.class
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{...}
// FixDiscountPolicy.class
@Component
public class FixDiscountPolicy implements DiscountPolicy{...}
이렇게 사용하고 beanScan에 여러개가 걸리면
@Primary
가 붙은 이거 하나를 가져온다는 것이다.
예를 들어 굉장히 다양한 DB가 있다고 가정할 때 mainDB에 이걸 붙이면 된다. 그리고 만약 primary가 아닌 다른 DB를 갖고오고 싶을 땐 그때 Qualifier를 쓴다든지, 직접 이름을 지정하여 갖고오든지 하면 된다.
@Primary
와 @Qualifier
가 같이 붙어있다면 뭐가 먼저일까? => 항상 자동보다는 수동이, 넓은 선택보다는 자세한 선택이 우선되기 때문에 Qualifier가 더 우선시 된다.