Programming-[Backend]/Spring

[수정중][스프링 기초] 14. 여러 개의 빈이 있을 때 자동주입하기, @Primary, @Qualifier, NoUniqueBeanDefinitionException

컴퓨터 탐험가 찰리 2021. 7. 7. 14:06
728x90
반응형

 

 

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으로 받아오므로, 컴파일 전에 확인이 안된다. 그래서 혹시라도 오타가 있거나 잘못된 부분이 있을 수 있는데, 어노테이션을 직접 만들어서 적용해서 이런 문제를 미연에 방지할 수 있다.

오타라고 표시는 해주지만, Compile 자체가 막히진 않는다.


자바 클래스를 만들 듯이, [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

728x90
반응형