
김영한 강사님의 스프링 핵심 원리의 강의 내용과 자료를 이용했음을 밝힙니다.
스프링에서 @Autowired를 통해 자동주입을 할 때, 타입으로 조회한다.
그러면 여기서 드는 의문!
"만약 같은 Type이 2개 이상이면 어느걸 주입할까?"

결과는 NoUniqueBeanDefinitionException이 발생한다.
그러면 같은 타입이 2개 이상이면 들어갈 빈을 지정해줘야겠다.
- @Autowired 필드 명 매칭
- @Qulifier
- @Primary
위 3가지 방법으로 지정이 가능하다.
참고로 @Primary 방법을 사용하는게 가장 깔끔하고 편하다.(혹시 앞에 방법만 보고 돌아갈까봐.. )
이 방식은 빈을 선택하는 방식을 추가하는 것인데
기본적으로 타입 매칭을 시도하고 주입될 수 있는 빈이 복수개이면 필드 이름, 파라미터 이름과 같은 빈을 찾아서 넣는 것이다.
예시를 보면
@Autowired
private DiscountPolicy rateDiscountPolicy;
이렇게 하면 DiscountPolicy 타입이 여러개더라도 그중에 rateDiscountPolicy라는 이름의 빈을 찾아와서 주입을 한다.
private DiscountPolicy discountPolicy;
@Autowired
public constructor(rateDiscountPolicy){//생성자
this.discountPolicy = rateDiscountPolicy;
}
이런식으로 생성자의 파라미터 이름으로도 찾는다.
필드 명, 파라미터 명 중 하나만 이름 맞춰줘도 잘 찾아주니 다 바꿔야 할지 걱정은 안해도 된다.
그런데 이렇게 했는데도 빈이 복수개여서 나타난 오류가 안해결 되는 경우가 있다.
그런 경우에는 위에서 말한 NoUniqueBeanDefinitionException이 아니라 UnsatisfiedDependencyException이 발생해서 그렇다.
UnsatisfiedDependencyException은 @Autowired 필드 명 매칭으로는 안되니 밑의 2개의 방법으로 해결하면 된다.
이건 위에서 본 @Autowired 방법이랑은 다르게 특정 빈을 아예 지정해버리는 방식이다.
우선 등록하고 싶은 빈에다가 @Qualifier랑 내가 사용하고 싶은 일종의 key를 적는다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy{}
그리고 자동 주입하는 곳에도 이 Qualifier를 사용한다.
@Autowired
public constructor(@Qualifier("mainDiscountPolicy") rateDiscountPolicy){//생성자
this.discountPolicy = rateDiscountPolicy;
}
이런 식으로 똑같이 넣어주면 같은 Qualifier 값(key 값)을 가지고 있는 빈을 가져온다.
추가로 말하자면 혹시 @Qualifier("mainDiscountPolicy")를 못찾으면 mainDiscountPolicy라는 이름의 빈을 찾는다.
하지만 괜히 남이 이해하기 어렵게 만들지 말고 그냥 핵심 기능에 맞게 사용하자!
그냥 @Primary 붙은 얘가 우선순위를 가져간다.
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
이렇게 같은 타입의 빈 두 개가 있다면
@Component
@Primary //check here!!
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
이렇게 @Primary 붙은 빈이 다른 빈 보다 우선순위를 가져서 주입이 된다.
그러면 둘 다 사용하면 누가 이길까?
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("mainDiscountPolicy")//생성자 주입의 생성자에 똑같은 Qualifier 붙였다고 가정
public class FixDiscountPolicy implements DiscountPolicy {}
늘 그렇듯이 좀 더 자세하게 규정한 것이 이긴다 -> @Qualifier 붙은 빈이 주입된다.
사실 한가지 방법이 더 있다.
직접 애노테이션을 만들면 되는데
새 창(?) 만들 때, class, interface, enum 이런 목록 중에 annotation이 있을 것이다.
그걸 만들면 끝이다.
강의에서 만든 애노테이션을 보자면
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
이렇게 만들었고 이 애노테이션 위에 있는 Target~Qualifier까지 모든 기능이 이 @MainDiscountPolicy에 들어가게 된다.
즉 @Qualifier("mainDiscountPolicy")가 있으니 @Qualifier("mainDiscountPolicy") 대신해서 @MainDiscountPolicy를 사용할 수 있는 것이다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy{}
@Component
@MainDiscountPolicy
public class RateDiscountPolicy{}
두 개가 same ~ same
근데 @Qualifier가 있는데 굳이 이렇게 애노테이션을 만드는 것은 유지보수에 혼란을 더할 수 있으니 정말 필요할 때만 하자.
사실 이건 좀 다른 이야기이긴 한데 새로운 글에서 말하기에는 너무 간단해서 여기에 살짝 넣어서 말하겠다.
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
this.policyMap = policyMap;
this.policies = policies;
System.out.println("policyMap = " + policyMap);
System.out.println("policies = " + policies);
}
이런 식으로 하면 policyMap와 policies에 타입이 맞는 모든 빈들이 저장된다.
이걸 이용해서 클라이언트에서 빈을 선택하게 해서 파라미터로 받은 뒤, Map에서 찾아서 알맞은 기능을 처리하게 할 수도 있다.
이런 식으로!
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}//
클라이언트가 discount 메소드를 이용해 원하는 빈 이름을 discountCode 파라미터로 보낼 수 있다.