1. 여러 개의 빈이 존재할 때 에러 발생 : NoUniqueBeanDefinitionException
@Autowired 어노테이션은 빈을 타입을 기준으로 조회한다. 그래서 두 개 이상의 같은 타입의 빈이 존재할 때는 NoUniqueBeanDefinitionException이 발생한다. 아래 코드로 확인해보자.
PlanServiceImpl 클래스는 CreatureRepository와 CaringPolicy를 생성자 주입받도록 되어있는 상황이다. 이중 CaringPolicy는 FixedCaringPolicy, RateCaringPolicy의 2개의 구현체를 갖고 있다.
각 구현체는 @Component 어노테이션을 통해 @ComponentScan에 의해 Scan되며, CaringPolicy 인터페이스를 implements로 상속받는다.
@Configuration과 @ComponentScan이 설정되어있는 AutoAppConfig를 파라미터로 갖는 ApplicationContext를 생성하고, PlanService를 조회하면 NoUniqueBeanDefinitionException 에러가 발생하는 것을 확인할 수 있다.
PlanServiceImpl 구현체에 CaringPolicy가 아니라 하위 타입인 FixedCaringPolicy 또는 RateCaringPolicy를 주입하는 것은 구현체를 직접 주입하는 것이 되어 DIP를 위반하게 된다. 이를 해결하기 위해서 @Qualifier, @Primary 어노테이션을 사용할 수 있다.
2. 자동 주입 될 빈 지정해주기 : @Qualifier, @Primary
2-1. Autowired의 기본 성질 이용
@Autowired는 타입에 해당하는 빈이 여러 개이면, 필드명과 파라미터를 통해 주입될 빈을 찾는다. PlanServiceImpl 클래스의 생성자 부분에서, CaringPolicy의 파라미터명을 fixedCaringPolicy로 변경하고 테스트하면, fixedCaringPolicy가 잘 주입되어 테스트가 통과하는 것을 확인할 수 있다(필드명은 caringPolicy를 하든, fixedCaringPolicy를 하든 상관없다. 생성자의 this.caringPolicy부분과 필드명이 일치하기만 하면 된다).
2-2. @Qualifier 어노테이션 이용
@Qualifier 어노테이션을 적용해서 특정 빈을 지정해줄 수 있다. 지정할 fixedCaringPolicy 빈의 클래스 상단에 @Qualifier 어노테이션을 붙이고, 주입받을 PlanServiceImpl 빈의 CaringPolicy 파라미터 부분에 @Qualifier 어노테이션을 붙여주면 된다.
FixedCaringPolicy 빈에 @Qualifier("qualifiedCaringPolicy") 적용
주입받을 PlanServiceImpl의 파라미터에 @Qualifier("qualifiedCaringPolicy") 적용
@Qualifier 어노테이션의 추가기능
만약 PlanServiceImpl에 @Qualifier를 적용했는데 @Qualifier("qualifiedCaringPolicy")가 적용된 빈을 찾지 못한다면, "qualifiedCaringPolicy"라는 이름을 가진 빈을 찾는다. 그러나 이런 추가 기능은 혼선을 불러올 수 있으므로 사용하지 않는 것이 좋다.
2-3. @Primary 어노테이션 적용
@Primay 어노테이션은 용어에서 알 수 있듯이 여러 개의 빈이 존재할 때 자동주입될 빈 중 최우선 순위의 빈에 적용하여 빈을 주입하는 방식이다.
만약 @Primary와 @Qualifier가 동시에 적용되어 있다면, @Qualifier가 우선적으로 적용된다. 항상 범위가 넓고 자동으로 적용되는 부분보다는, 범위가 좁고 수동으로 적용되는 부분이 우선순위를 갖는다. 따라서 주로 사용하는 빈에 @Primary를 적용해두고, 필요시에는 보조로 사용하는 빈에 @Qualifier를 붙여서 사용하면 된다.
-> 주석 : 명확한 의존관계 표현를 위해서 @Primary 와 @Qualifier를 동시 적용하기보다는 @Qualifier만 적용하는 것도 좋을 것 같다.
3. 어노테이션 만들기
@Qualifier는 속성값으로 빈의 이름을 String으로 받아오므로, 컴파일 전에 확인이 안된다. 그래서 혹시라도 오타가 있거나 잘못된 부분이 있을 수 있는데, 어노테이션을 직접 만들어서 적용해서 이런 문제를 미연에 방지할 수 있다.
자바 클래스를 만들 듯이, [Alt + insert] 키를 눌러서 어노테이션을 만들 수 있다. 어노테이션은 아래 코드에서 볼 수 잇듯이 @interface라고 표시된다. 해당 어노테이션 위에 @Qualifier 에서 사용하는 @Targer, @Retention, @Inherited, @Documented 어노테이션을 그대로 복사해와서 쓰고, @Qualifier 어노테이션을 이름을 지정하여 사용해준다.
Qula ... 오타 고치기
그리고 나서 해당 어노테이션을 @Qualifier("빈 이름") 대신 사용해준다. 아래에서 볼 수 있는 것처럼, 어노테이션의 이름 자체를 잘못쓰면 컴파일러가 미리 오류를 잡아주기 때문에 유용한 방식이라고 할 수 있다.
4. List, Map으로 모든 빈을 한번에 조회하기
수정하기 : AutoAppConfig.class에 대한 코드가 없음, 이해할 수 있도록 다시 작성필요
웹 애플리케이션의 빈들을 클라이언트가 선택할 수 있는 상황이 있을 수 있다. 예를 들어서 5000원 할인 쿠폰 또는 10% 할인 쿠폰을 선택할 수 있을 수 있다. 이럴 때는 Map 또는 List로 빈들을 정의하고 결과값을 도출하는 메서드를 사용하면 된다. 테스트 코드 내에 CaringPolicy를 Map과 List 형태로 주입받는 CaringService를 static으로 정의해보자.
@Autowired로 caringPolicyMap과 policies를 자동 주입받는다. 그리고 CaringPolicy가 @ComponentScan에 의해 스캔되도록 ApplicationContext를 정의할때 AutoAppConfig.class와 CaringService.class를 둘 다 등록하도록 한다. 그리고 Creature, price, policy에 따라서 비용이 산출되는 expense 메서드를 만든다.
아래 예제에서는 policy 인자가 fixedCaringPolicy로 지정되어 있다. 그리고 40번째 줄에서 이 policy값을 통해 caringPolicyMap의 key값으로 이용한다. 즉 fixedCaringPolicy 빈을 꺼내어 caringPolicy로 적용하는 것이다. 이렇게 Map 형태로 빈들을 꺼내와서 다른 값들을 적용할 수 있다! 동적으로 빈들을 적용할 수 있게 된 것이다.
앞에서 정의한 바에 따라 fixedCaringPolicy는 Creature의 Grade가 ENDANGERED이면 fixedCaringPolicy인 경우 10,000원을 더하고, rateCaringPolicy인 경우 10%를 더하도록 되어있다. expense 메서드의 인자로 어떤 caringPolicy를 적용하느냐에 따라 다른 결과가 도출되는 것을 확인하였다.
참조
1. 인프런_스프링 핵심 원리 기본편_김영한 님 강의
www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8/dashboard
'Programming-[Backend] > Spring' 카테고리의 다른 글
[스프링 기초] 16. 빈 스코프 : Prototype Scope와 Provider (0) | 2021.07.11 |
---|---|
[스프링 기초] 15. 빈 생명주기 콜백 : initMethod, destroyMethod, @PostConstruct, @PreDestroy (0) | 2021.07.11 |
[스프링 기초] 13. 의존관계 주입 : 4가지 방법 (0) | 2021.07.04 |
[스프링 기초] 12. 컴포넌트 스캔(Component Scan) (0) | 2021.07.04 |
[스프링 기초] 11. 싱글톤(Singleton) (0) | 2021.06.30 |