이번 시간에는 조회할 빈이 2개 이상인 문제에 대해서 알아보았다.
먼저 예시로 OrderServiceImpl을 보자.
@Autowired는 기본적으로 타입으로 조회를 한다.
생성자가 하나밖에 없기 때문에 생략되었지만, 생성자에는 @Autowired 애노테이션이 달려있는 것과 같다.
생성자의 파라미터인 discountPolicy는 타입인 DiscountPolicy로 조회를 할 것이다.
그렇기 때문에 아래와 같은 코드와 유사하게 동작한다. (실제로는 더 많은 기능을 제공함)
ac.getBean(DiscountPolicy.class)
스프링 빈 조회에서 배웠듯이, 타입으로 조회하면 선택된 빈이 2개 이상일 때 문제가 발생한다.
DiscountPolicy에는 하위 타입인 FixDiscountPolicy와 RateDiscountPolicy 가 있는데....
둘 다 스프링 빈으로 선언해보자.
원래는 RateDiscountPolicy 만 @Component 어노테이션이 달려있었는데,
FixDiscountPolicy 에도 @Component 어노테이션을 추가해보자.
이러면 둘 다 빈 등록이 될 것이다.
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy
테스트를 돌려보면 위와 같은 오류가 난다.
오류 내용을 읽어보면, 매치되는 빈을 하나를 기대했는데 fixDiscountPolicy,rateDiscountPolicy 2개를 발견해서 오류가 났다고 한다.
오류를 해결하기 위해 하위 타입으로 지정할 수도 있지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성이 떨어진다.
그리고 이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있거나 자식이 있을때도 해결이 되지 않는다.
스프링 빈을 수동 등록해서 문제를 해결해도 되지만, 의존 관계 자동 주입에서 해결하는 방법이 여러가지 있다.
여러개의 빈의 선택될 때, 어떻게 해야하는지,
@Autowired 필드 명, @Qualifier, @Primary 세가지 방법을 알아보자.
첫번째는, @Autowired 필드명 매칭이다.
말 그대로 Autowired에 필드명을 매칭시키는 것이다.
Autowired에는 특이한 기능이 있는데, Autowired는 처음에 타입 매칭을 시도한다.
그런데 결과가 2개 이상이 나오면, 필드 이름이나 파라미터 이름으로 빈 이름을 추가 매칭한다.
예를 들어, discountPolicy를 조회하면 타입인 DiscountPolicy로 조회를 할 것이다.
그러면 RateDiscountPolicy와 FixDiscountPolicy 두 개의 빈이 검색될 것이고,
그럴때는 파라미터 이름인 discountPolicy으로 두개의 빈 중에서 빈 이름이 똑같은게 있으면 그것을 찾아온다.
그리고 테스트 코드를 돌려보면 통과~~!
물론 필드주입도 위와 같은 식으로 가능하다.
정리를 하자면, @Autowired 매칭은 타입으로 조회를 하기 때문에,
조회된 타입이 하나라면 조건과 상관없이 빈객체를 가져와서 주입을 해준다.
하지만, 타입으로 조회했을 때, 같은 타입이거나 하위 자식객체들을 가져와서 그 매칭된 결과가 2개 이상일 때는,
필드 이름이나 파라미터 이름으로 빈 이름을 매칭해준다.
두번째는, @Qualifier 어노테이션을 사용하는 건데, @Qualifier 는 추가 구분자를 붙여주는 방법이다.
주입시에 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.
(구분할 수 있는 추가적인 옵션을 제공하는 것이다.)
RateDiscountPolicy와 FixDiscountPolicy에 @Qualifier 어노테이션을 추가해준다.
그리고 원하는 파라미터에 @Qualifier("mainDiscountPolicy")를 위와 같이 추가해주면 사용할 수 있다!
하지만, 예를 들어, Qualifier로 주입을 하는데, mainDiscountPolicy가 없어서 못찾을 경우에는?
그러면 mainDiscountPolicy 라는 이름의 스프링 빈을 추가로 찾는다.
하지만 Qualifier는 Qualifier 를 찾는 용도로만 사용하는게 혼란을 야기하지 않고 명확하기 때문에 좋다.
Qualifier를 정리하자면, Qualifier 끼리 매칭을 하고, 없으면 빈 이름으로 매칭을 시도하고,
또 없으면 NoSuchBeanDefinitionException 예외가 발생한다.
세번째는, @Primary 사용하는 것이다.
@Primary 는 우선순위를 정하는 방법이다.
Autowired 시에 여러 빈이 매칭되고, 어떤 빈이 @Primary 가 지정이 되어 있으면 그 빈이 우선권을 가진다.
Ctrl + Alt + B를 눌러서 인터페이스의 구현체들을 바로 확인해 볼 수가 있다.
RateDiscountPolicy로 들어가서 @Primary 어노테이션을 추가해준다.
그러면 RateDiscountPolicy가 우선권을 가지게 된다.
그 과정을 살펴보면, DiscountPolicy를 타입으로 조회를 하고, 여러개의 빈이 검색이 되지만,
Primary가 붙어있으면 해당 되는 빈이 우선순위를 가지면서 의존관계 주입이 된다.
그래서 Qualifier랑 Primary 중에 무엇을 써야 되는가?
Qualifier의 단점은 주입을 받을 때 모든 코드에 @Qualifier를 붙여줘야 된다는 것이다.
그래서 둘 다 상황에 맞게 활용을 하는 것이 좋을 것이다.
Primary와 Qualifier가 실무에 활용되는 예시를 들어보자면, 메인DB가 있고 보조DB가 있다고 가정하자.
메인DB가 9, 보조DB가 1의 비율으로 로직이 쓰인다면, 메인DB 커넥션 빈을 가져오는 코드에는 Primary를 걸어주고,
보조DB는 Qualifier를 쓰거나 이름을 지정해서 가져오도록 내부적으로 룰을 정할 수 있을 것이다.
우선순위는 Qualifier랑 Primary 둘 중 어떤것이 높을까?
프론트 엔드쪽에서도 보면, class보다 id로 지정하는게 우선순위가 높듯이, 항상 세부적인게 우선권을 가져간다.
마찬가지로, 스프링은 자동보다는 수동이, 넓은 범위의 선택권보다는 좁은 범위의 선택권이 우선순위가 높다.
그렇기 때문에 세세하게 이름을 지정해주는 @Qualifier 가 우선순위가 높다.
댓글