스프링 핵심 원리 기본편 (5. 조회 할 Bean이 2개 이상이면?)
@Autowired
private DiscountPolicy discountPolicy
DiscountPolicy의 하위 타입 FixDiscountPolicy, RateDiscountPolicy를 @Component를 통해서 스프링 빈으로 선언해보자.
@Component
public class FixDiscountPolicy implements DiscountPolicy{
@Component
public class RateDiscountPolicy implements DiscountPolicy{
그리고 실행해보면
NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
이라는 오류가 나왔다.
하나의 빈을 기대했는데 fixDiscountPolicy , rateDiscountPolicy 2개가 발견되었다고 말해주는 오류...
이를 해결하기 위한 방법은 무엇이 있을까?
1. @AutoWired 필드 명 매칭.
@Autowired
private DiscountPolicy rateDiscountPolicy
필드 명이 rateDiscountPolicy 이므로 정상 주입된다.
(파라미터에 빈 이름을 매칭해도 됨.)
2. @Qualifier 사용
일단 빈 등록 시 @Qualifier를 붙여주자.
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy{
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy{
그리고 나서, 주입 시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
3. @Primary 사용
@primary는 우선권을 정하는 방법이다.
@Autowired 시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{
@Autowired //의존관계 자동 주입을 위해 @Autowired 사용.
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다.
이런 경우 어떤 것이 우선권을 가져갈까? 스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다.
4.애노테이션 직접 만들어서 사용
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy{
@Autowired //의존관계 자동 주입을 위해 @Autowired 사용.
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier 뿐만 아니라 다른 애노테이션도 모아서 사용할수 있다. → 스프링의 편리한 기능!
조회한 빈이 모두 필요할 땐 List나 Map을 사용하자.
public class AllBeanTest {
@Test
void findAllBean(){
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L,"memberA", Grade.VIP);
int fixdiscountPrice = discountService.discount(member,10000,"fixDiscountPolicy");
Assertions.assertThat(discountService).isInstanceOf(DiscountService.class);
Assertions.assertThat(fixdiscountPrice).isEqualTo(1000);
int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
Assertions.assertThat(rateDiscountPrice).isEqualTo(2000);
}
static class DiscountService{
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);
}
public int discount(Member member, int price, String discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member,price);
}
}
}
DiscountService는 Map으로 모든 DiscountPolicy 를 주입받는다.
이때 fixDiscountPolicy , rateDiscountPolicy 가 주입된다. discount () 메서드는 discountCode로 "fixDiscountPolicy"가 넘어오면 map에서 fixDiscountPolicy 스프링 빈을 찾아서 실행한다.
물론 “rateDiscountPolicy”가 넘어오면 rateDiscountPolicy 스프링 빈을 찾아서 실행한다.
new AnnotationConfigApplicationContext() 를 통해 스프링 컨테이너를 생성한다.
AutoAppConfig.class , DiscountService.class 를 파라미터로 넘기면서 해당 클래스를 자동으로 스프링 빈으로 등록한다. 스프링 컨테이너를 생성하면서, 해당 컨테이너에 동시에 AutoAppConfig , DiscountService 를 스프링 빈으로 자동 등록한다. 이후 Assertions.assertThat으로 코드를 검증한다.