@Qualifier와 @Primary

Hansu Kim·2022년 2월 20일
0

Spring boot

목록 보기
7/10

인터페이스를 기준으로 getBean시 여러 구현체 빈들이 탐색되는 문제

@Autowired는 기본적으로 타입 매칭을 시도한다.

@Autowired
private DiscountPolicy discountPolicy

위의 코드는 @Autowired 내부 로직 중 아래 getBean 부분의 로직이 수행된다.

ApplicationContext ac = new AnnotationConfigApplicationContext(AppConfig.class);
DiscountPolicy temp = ac.getBean(DiscountPolicy.class);

위 코드에서 DiscountPolicy는 인터페이스로 여러 구현체를 가지고 있고, 각 구현체들은 @Component 등의 어노테이션을 통해 빈으로 등록되어있으므로, 스프링에선 어떤 빈을 가져와야할지 선택할 수 없기에 NoUniqueBeanDefinitionException 에러가 발생한다. 이 때 해결 방법으로는 아래와 같은 방안들이 있다.

  • @Autowired의 필드명 매칭 활용
  • @Qualifier 사용
  • @Primary 사용

@Autowired의 필드명 매칭 활용

@Autowired가 붙은 코드의 변수명을 변경하면, 스프링에서 해당 객체를 탐색할 때 필드명을 기반으로 매칭하게 된다.

@Autowired
private DiscountPolicy rateDiscountPolicy

그에 따라, 위와 같이 @Autowired가 설정된 인터페이스 타입의 변수에, 변수명을 해당 인터페이스의 구현체로 설정해주면 자동으로 getBean시, 인터페이스의 구현체 중 필드명과 동일한 구현체를 기준으로 빈이 선택된다.

@Qualifier 활용

Qualifieir를 사용하면, 빈 이름을 탐색할 때 사용되는 추가적인 구분자를 제공해줄 수 있다. Qualifier는 빈 이름을 변경하는게 아니라, 추가 구분자를 제공해주는 것임을 유의해야 한다.

@Component
@Qualifier("fixDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy
@Autowired
private @Qualifier("fixDiscountPolicy") DiscountPolicy rateDiscountPolicy

위와 같이 사용해서 인터페이스의 여러 구현체 중 선택되는 빈을 지정해줄 수 있다.
만약 Qualifier를 통해 제공된 구분자를 통해서 빈을 탐색할 수 없다면, 위 코드를 기준으로 필드명 기준으로 빈 탐색이 수행되고, 필드명 기준으로도 매칭되는 빈이 없다면 NoSuchBeanDefinitionException이 발생한다.
하지만 해당 방식은 명확하지 않으므로 @Qualifier는 @Qualifier 탐색 용도로만 구현하는 것이 적절하다.

참고로, Qualifier는 문자열을 통해 설정되기에 컴파일 타임에서 타입 체크가 되지 않는다. 그에 따라 실무에서 Qualifier는 별도 Annotation으로 선언하여 사용하는 것이 효율적이다.

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER,
  ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {  }

위와 같이 별도 Annotation으로 지정해놓고 아래와 같이 활용한다면, 어노테이션 단위로 타입체크가 발생하여 컴파일시 오류를 발생시킬 수 있다.

@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {}
//생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                          @MainDiscountPolicy DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
  }

@Primary

Primary는 우선순위를 정하는 방법이다. 구현체들에 @Primary를 붙여놓는다면, 자동으로 인터페이스에 대한 getBean시 Primary가 붙은 구현체가 선택된다.

만약 Qualifier와 Primary 중에서는 Qualifier가 우선 순위가 있다.

@Primary vs @Qualifier

각각의 방식에서 장단점이 있다. Primary는 간편하게 설정하여 쓸 수 있고, Qualifier는 세부적으로 설정할 수 있다.

App에서 Main DB와 Sub DB가 있다고 가정할 때, Main DB는 Primary로 설정하고 Sub는 Qualifier로 접근하도록 구현한다면 특별한 경우에만 서브 디비로 동작하도록 로직을 구현할 수 있다.

0개의 댓글