Spring/스프링 핵심원리 기본편

[Spring] 의존관계 자동 주입(2/2) - @Autowired, @Qualifier, @Primary

딸기케잌🍓 2021. 10. 10. 12:22

의존성 주입을 위해 빈을 주입할 때 조회된 빈이 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 매칭
  1. 타입매칭
  2. 타입 매칭으로 빈이 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 매칭
  1. @Qualifier끼리 매칭
  2. @Qualifier과 같은 빈 이름으로 매칭
  3. 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 우선순위가 높습니다.