조회 빈이 2개 이상일 때

@Autowired
private DiscountPolicy discountPolicy

@Autowired는 타입으로 조회하기 때문에 ac.getBean(DiscountPolicy.class)와 유사하게 동작한다.

그런데 이 코드는 타입으로 조회할 때 선택된 빈이 2개 이상이면 문제가 발생한다.

@Component
public class FixDiscountPolicy implements DiscountPolicy {

}

@Component
public class RateDiscountPolicy implements DiscountPolicy {

}

public class Client {

  // 의존성 주입 실패
  @Autowired
  private DiscountPolicy discountPolicy;
}
NoUniqueBeanDefinitionException: No qualifying bean of type
'hello.core.discount.DiscountPolicy' available: expected single matching bean
but found 2: fixDiscountPolicy,rateDiscountPolicy

이렇게 DiscountPolicy의 하위 타입 2개를 모두 스프링 빈으로 선언하면 NoUniqueBeanDefinitionException 오류가 발생한다.

의존성 주입하는 부분에서 DiscountPolicy 대신 하위 타입으로 지정할 수도 있지만 이런 방법은 DIP를 위배하고 유연성이 떨어진다. 게다가 이름만 다르고 완전히 똑같은 타입의 스프링 빈이 2개 있는 상황이라면 해결되지 않는다.

@Autowired 필드 명 매칭

@Autowired
private DiscountPolicy discountPolicy;

필드 명을 rateDiscountPolicy로 지정해줬으므로 정상적으로 주입된다. 필드명 매칭은 먼저 타입 매칭을 시도한 뒤 그 결과에 여러 빈이 존재할 때 추가로 동작하는 기능이다.

  1. 타입 매칭

  2. 2개 이상일 경우 필드 명, 파라미터 명으로 빈 이름 매칭

@Qualifier

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {

}

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {

}

@Qualifier를 붙여 구분해준다. 주입할 때 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.

public class OrderServiceImpl {

  @Autowired
  public OrderServiceImpl(MemberRepository memberRepository,
      // 주입할 빈 지정
      @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
  }
}

@Qualifier로 주입할 때 @Qualifier("mainDiscountPolicy")를 못찾으면 mainDiscountPolicy라는 이름의 스프링 빈을 추가적으로 찾는다. 하지만 경험상 @Qualifier@Qualifier를 찾는 용도로만 사용하는 게 명확하고 좋다.

  1. @Qualifier끼리 매칭

  2. 빈 이름 매칭

  3. 없으면 NoSuchBeanDefinitionException 발생

@Primary

우선 순위를 정하는 방법이다. @Autowired할 때 여러 빈이 매칭되면 @Primary 붙은 빈이 우선권을 가진다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {

}

@Component
public class FixDiscountPolicy implements DiscountPolicy {

}

@Primary@Qualifier 중에 어떤 것을 선택하는 게 좋을까?

public class OrderServiceImpl {

  @Autowired
  public OrderServiceImpl(MemberRepository memberRepository,
      @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    this.memberRepository = memberRepository;
    this.discountPolicy = discountPolicy;
  }
}

@Qualifier는 주입 받을 때 모든 코드에 애너테이션을 붙여줘야 하는 단점이 있다. 반면에 @Primary는 이렇게 붙일 필요가 없다.

메인 DB와 서브 DB가 존재할 때 메인에 @Primary를 적용해 @Qualifier 지정 없이 편하게 조회하고, 서브를 읽을 때는 @Qualifier를 이용해 명시적으로 가져오면 깔끔하게 사용할 수 있다.

스프링은 자동보다 수동이, 넓은 선택권 보다는 좁은 선택권이 우선 순위가 높다. @Primary는 기본값처럼 동작하지만 @Qualifier는 매우 상세하게 동작하는 기능이므로 @Qualifier가 우선권이 높다.

Last updated