우리가 @Autowired를 써서 주입할 때, type으로 빈을 찾게 된다. ac.getBean(DiscountPolicy.class) 와 유사하게 동작하는 것이다. 그렇다면 타입으로 조회했을 때 빈이 2개 이상이면 문제가 발생한다.
NoUniqueBeanDefinitionException 오류가 발생한다.
- @Autowired 필드 명 매칭
- @Qualifier
- @Primary
크게 위처럼 세 가지 방법으로 문제를 해결할 수 있다.
아래에 설명을 위해 예시 상황을 설명하자면 DiscountPolicy ➡️ FixDiscountPolicy, RateDiscountPolicy, DiscountPolicy를 상속받아서 할인 정책을 만든다. 그래서 DiscountPolicy 타입으로 조회를 하면 두가지 빈이 조회가 된다.
@Autowired는 기본적으로 타입 매칭 ➡️ 빈이 여러개 나오면 필드 이름, 파라미터 이름으로 빈 이름 매칭 순으로 동작한다.
우선 타입으로 빈을 찾고 여러개가 나오면, 이름까지 체크해보는 것이다.
@Autowired
private DiscountPolicy rateDiscountPolicy
빈 이름은 그대로 사용하면서, @Qualifier라는 구분자를 붙여주는 방식이다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
빈 등록할 때 @Qualifier를 붙여준다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
주입할 때는 @Qualifier("등록한 이름")을 적어준다. 이때도 "mainDiscountPolicy"를 못 찾으면 mainDiscountPolicy라는 이름을 가진 스프링 빈을 찾는다. 동작은 이렇게 하지만, 이를 일부러 의도해서 쓰지는 말 것.
@Qualifier끼리 매칭 ➡️ 못 찾으면 빈 이름으로 매칭 ➡️ 그래도 못 찾으면 예외 발생
@Component를 통한 컴포넌트 스캔 말고, 직접 @Bean으로 빈 등록할 때도 @Qualifier는 사용 가능하다.
말 그대로 @Primary가 우선권을 가진다.
예를 들어, rate DiscountPolicy가 우선권을 가지게 하려면 다음과 같이 하면 된다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
그럼 둘 중에 뭐를 써야할까? @Qualifier의 단점은 @Qualifier를 붙여줘야 한다는 점이다. 예를 들어 자주 사용하는 건 @Primary로 @Qualifier 지정 없이 편하게 사용하고, 가끔 쓰는 건 @Qualifier 지정을 통해서 명시적으로 사용할 수도 있다.
둘 중에 우선 순위는 @Qualifier가 우선권이 높다.
대부분의 경우 수동 > 자동, 좁은 범위 > 넓은 범위 로 우선순위가 높다.