본문 바로가기

web +a

@Autowired 조회 빈이 2개 이상일 때 | 문제점 해결 & 해당 타입 빈 모두 보기

@Autowired도 ac.getBean(XXX.class)처럼 Type으로 조회하기 때문에

해당 타입 빈이 유일하지 않다면 문제다.

 

예시) 

만약 생성자에서 DiscountPolicy를 주입받는 상황이라고 하자. (by @Autowired)

그런데 DiscountPolicy의 하위 클래스 중 @Component인 클래스가 2개일 때,

그러니까 FixDiscountPolicy와 RateDiscountPolicy로 2개가 스프링빈으로 등록되어있을 때,

@Autowired 의존관계 자동 주입시 NoUniqueBeanDefinitionException 오류가 발생한다. 

 


 

이러한 의존관계 자동 주입시 동일타입 빈 문제를 어떻게 해결할까?

3가지 방법이 있는데, @Primary를 많이 사용한다. 

나머지 방법들도 사용할 상황이 있으까 이런게 있구나하고 알아두자.

 

 

@Autowired 타입매칭시 여러 빈이 있을 때 해결하는 방법

1. 필드이름, 파라미터 이름으로 빈을 매칭한다.

2. @Qualifier 사용 → @Qualifier에서 없으면 빈이름으로 매칭 시도

3. @Primary 사용

 

 

cf. @Primary는 기본값처럼 동작한다. 따라서 비교적 수동 설정이 필요한 @Qualifier가 우선권이 높다.

 


 

특정 타입 빈을 모두 조회하고 싶을 수도 있다. 

예를 들면 DiscoutPolicy를 모두 조회해서

user에게 FixDiscountPolicy, RateDiscountPolicy에 대한 선택권을 주고 싶을 경우.. 

 

 

∨ 특정 타입 빈을 Map, List를 통해 모두 조회하는 예시

아래 코드를 보자. 

컨테이너 ac에 방금 새로 작성한 DiscountService를 전달했다. 

DiscountService의 생성자에 @Autowired를 사용했고, 그 파라미터에는 Map과 List를 작성했다. 

Map의 value와 List 타입은 DiscountPolicy로 지정했다. 

이 경우 모든 하위타입이 잘 @Autowired되어서,

모든 DiscountPolicy 하위타입이 Map, List로 쭈루룩 나타난다.

public class AllBeanTest {

    @Test
    void findAllBean() {
        ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
    }

    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);
        }
    }
}

 

즉, 결과가 다음과 같이 나타난다.

DiscountPolicy 타입을 @Autowired 했을 때 하위타입이 Map, List로 잘 표현되었다.

 

policyMap = {fixDiscountPolicy=hello.core.discount.FixDiscountPolicy@246f8b8b, rateDiscountPolicy=hello.core.discount.RateDiscountPolicy@278bb07e}
policies = [hello.core.discount.FixDiscountPolicy@246f8b8b, hello.core.discount.RateDiscountPolicy@278bb07e]

 


Test코드를 작성해보자. 

 

∨ 매개변수에 멤버, 가격, 할인정책key를 넣으면 이전에 만든 Map<String, DiscountPolicy> policyMap에서 해당 정책을 꺼내 discountPrice를 꺼내주는 메서드인 DiscountService.discount를 구현했다. 

cf. TDD로 구현해보았다.

 

∨ 그리고 assertThat(discountPrice).isEqualTo(1000), assertThat(ratediscountPrice).isEqualTo(2000)을 통해서 각각의 할인 정책이 잘 기능하는지 검증완료했다. 

 

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, "userA", Grade.VIP);
        int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");

        assertThat(discountService).isInstanceOf(DiscountService.class);
        assertThat(discountPrice).isEqualTo(1000);

        int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
        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);
        }
    }
}

 

 

∨ 동적으로 빈을 선택해야 할 때 이런식으로 Map과 다형성을 사용하면 좋다

 


 

반응형
다른 블로그