의존성 주입을 위해 빈을 주입할 때 조회된 빈이 2개 이상일 경우가 있습니다.
다음과 같은 경우입니다.
@Autowired는 빈의 타입으로 빈을 조회하는데,
@Component
public class FixDiscountPolicy implements Discountpolicy{}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
위와같이 DiscountPolicy 인터페이스를 FixDiscountPolicy와 RateDiscountPolicy가 구현하고 있을 경우
@Autowired
private DiscountPolicy discountPolicy
DiscountPolicy 타입으로 빈을 주입받으려고 하면 NoUniqueBeanDefinitionException 오류가 발생합니다.
NoUniqueBeanDefinitionException:
No qualifying bean of type 'hello.core.discount.DiscountPolicy' available:
expected single matching bean but found 2
: fixDiscountPolicy,rateDiscountPolicy
하나의 빈을 기대했는데 2개가 발견됐다고 친절하게 알려줍니다.
DiscountPolicy 대신 구체 클래스 타입으로 지정할 수도 있지만 이는 DIP를 위배하고 유연성도 떨어집니다.
또한 이름만 다르고 같은 구체 클래스 타입일 경우 위 오류를 해결할 수 없습니다.
이를 해결하기 위한 방법으로는 다음과 같은 세가지 방법이 있습니다.
- @Autowired 파라미터명 매칭
- @Qualifier로 빈에 구분자 추가
- @Primary로 우선순위 지정
@Autowired
@Autowired는 타입 매칭을 우선 시도하고 여러 빈이 있다면 필드 이름, 파라미터 이름으로 빈 이름을 매칭합니다.
@Autowired
private DiscountPolicy rateDiscountPolicy
DiscountPolicy 타입으로 두가지 빈이 매칭되지만 필드명이 rateDiscountPolicy이므로 해당 타입이 주입됩니다.
@Autowired 매칭
- 타입매칭
- 타입 매칭으로 빈이 2개 이상일 때 필드명, 파라미터 명으로 빈 이름 매칭
@Qualifier
빈 이름은 그대로이고, 추가 구분자를 빈에 붙여주는 방식입니다.
빈 주입시 해당 구분자로 빈을 구분하여 주입합니다.
빈 등록시 @Qualifier 어노테이션을 붙여줍니다.
<예제1 - @Qualifier 등록>
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
빈을 주입시 @Qualifier를 붙여주고 등록한 구분자를 적어줍니다.
<예제2 - @Qualifier를 이용한 생성자 자동 주입>
@Autowired
public OrderServiceImpl(MemberRepository memberRepository
,@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
빈 주입시 @Qualifier("mainDiscountPolicy")를 찾지 못하면 mainDiscountPolicy 라는 이름의 스프링 빈을 추가로 찾습니다.
이 때도 찾지 못한다면 NoSuchBeanDefinitionException 예외가 발생합니다.
@Qualifier는 @Qualifier를 찾는 용도로만 사용하는 것이 명확하고 좋습니다.
직접 빈 등록시에도 @Qualifier를 동일하게 사용할 수 있습니다.
<예제3 - @Bean등록시 @Qualifier 사용>
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
@Qualifier("mainDiscountPolicy")와 같이 사용할 때 문자열은 컴파일시 타입체크가 안되어
오타와 같은 잘못된 문자를 캐치하지 못합니다. 이를 해결하기위해 어노테이션을 직접 만들어 적용할 수 있습니다.
<예제4 - @MainDiscountPolicy 어노테이션 작성>
package hello.core.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
<예제5 - @MainDiscountPolicy 적용>
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
예제5와 같이 예제1,2,3의 @Qualifier("mainDiscountPolicy") 를 @MainDiscountPolicy로 대신할 수 있습니다.
어노테이션을 이용하면 컴파일시에 에러를 잡을 수 있고, 어디에 @MainDiscountPolicy가 적용되었는지 추적이 용이합니다.
어노테이션에는 상속이란 개념은 없고 여러 어노테이션을 모아서 사용하는 기능은 스프링이 제공해줍니다.
@Qualifier 매칭
- @Qualifier끼리 매칭
- @Qualifier과 같은 빈 이름으로 매칭
- NoSuchBeanDefinitionException 예외 발생
@Primary
@Autowired 시에 여러 빈이 매칭되면 @Primary 어노테이션이 붙은 빈이 우선권을 가지도록 할 수 있습니다.
<예제6 - @Primary로 우선순위 지정>
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
RateDiscountPolicy 클래스에 @Primary 어노테이션을 이용하여 RateDiscountPolicy가 우선권을 가지도록 할 수 있습니다.
<예제7 - @Primary가 동작하는 빈 주입 코드>
//생성자
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//수정자
@Autowired
public DiscountPolicy setDiscountPolicy(DiscountPolicy discountPolicy) {
return discountPolicy;
}
@Primary가 붙으면 문제 없이 우선권을 가진 빈이 주입됩니다.
@Qualifier와 @Primary 비교
@Qualifier는 주입 받을 때 모든 코드에 예제1, 2, 3처럼 @Qualifier를 써줘야 한다는 단점이 있습니다.
@Primary는 우선순위만 @Primary 어노테이션으로 명시해주면 빈을 주입하는 코드부분은 따로 건드리지 않아도 됩니다.
언제 무엇을 사용할까
디폴트로 쓰는 빈은 @Primary를 적용하여 쓰고 특별한 기능이 필요한 곳에 @Qualifier를 지정해서 명시적으로 쓰면 코드를 깔끔하게 쓸 수 있습니다.
우선순위
@Primary는 기본값 처럼 동작하고 @Qualifier는 매우 상세하게 동작합니다.
스프링은 자동보다 수동이, 넓은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 높으므로
@Qualifier 우선순위가 높습니다.
'Spring > 스프링 핵심원리 기본편' 카테고리의 다른 글
[Spring] 의존관계 자동 주입 - 생성자 주입을 선택하자! (0) | 2021.08.30 |
---|---|
[Spring] 컴포넌트 스캔 (1) | 2021.08.09 |
[Spring] 싱글톤 컨테이너 (0) | 2021.08.01 |