인프런 김영한 강의/스프링 핵심 원리 - 기본편

스프링 핵심 원리 기본편 (5. 조회 할 Bean이 2개 이상이면?)

고을마을 2022. 8. 7. 13:21

@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으로 코드를 검증한다.