클라우드 낚시꾼

[Spring] @Autowired 필드명, @Qualifier, @Primary 본문

BE Framework/SpringBasic

[Spring] @Autowired 필드명, @Qualifier, @Primary

KanuBang 2024. 3. 19. 15:17
728x90

1. 조회 빈이 2개 이상인 문제

// 1번 코드
@Autowired
private DiscountPolicy discountPolicy

// 2번 코드, ac는 스프링 컨테이너 변수명
ac.getBean(DiscountPolicy.class)

 

@Autowired는 타입(Type)으로 조회한다. 그렇기에 위의 1번 코드와 2번 코드는 사실상 같은 코드이다.

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

@Component
public class RateDiscountPolicy implements DiscountPolicy {}


//의존관계 자동주입 실행
@Autowired
private DiscountPolicy discountPolicy

 

DiscountPolicy의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy 둘다 스프링 빈으로 선언하였다. 이 상태에서 의존관계 자동주입을 실행하면, NoUniqueBeanDefinitionException 오류가 발생한다. 이 오류 메시지는 DiscountPolicy 타입으로 빈을 조회했을 때, 조회 결과로 하나의 빈을 기대했지만 fixDiscountPolicy, rateDiscountPolicy 2개의 빈이 발견되었다고 알려준 것이다. 이것이 NoUniqueBeanDefinitionException 오류이다.

@Autowired
private RateDiscountPolicy discounyPolicy

 

이때, 의존관계 자동주입 코드를 하위 타입으로 지정하여 에러를 막을 수 있다. 하지만, 하위 타입으로 지정하는 것은 DIP를 위배하고 유연성을 떨어지게 한다. 그리고 이름만 다르고 완전히 똑같은 스프링 빈이 2개 있을 때를 해결 할 수 없다. 이제부터 이러한 의존 관계 자동 주입 문제를 해결하는 방법을 알아보자.


2. 조회 대상 Bean이 2개 이상일 때 해결 방법

RateDiscounyPolicy, FixDiscountPolicy가 DiscountPolicy의 하위 타입이라고 가정한다.

#1. @Autowired 필드명

@Autowired의 매칭은 아래와 같이 진행된다.

  1. 타입 매칭
  2. 타입 매칭 시 여러 빈이 있으면
    • 필드 이름 매칭
    • 파리미터 이름 매칭
// 수정 전: 타입 매칭
@Autowired
private DiscountPolicy discountPolicy

// 수정 후: 타입 매칭 후 필드명 매칭 시도
@Autowired
private DiscountPolicy rateDiscountPolicy

 

수정 전, 수정 후 모두 DiscountPolicy 타입으로 Bean을 조회하기 때문에 Bean이 2개 이상 조회된다. 다음으로, 수정 전 코드는 discountPolicy 필드 이름으로 빈 조회를 시도한다. 이때도 역시 조회 결과로 Bean이 2개 이상 발견되고 최종적으로 NoUniqueBeanDefinitionException이 발생하게 된다. 반면, 수정 후 코드는 rateDiscountPolicy 필드 이름으로 빈 조회를 시도한다. 이때는 최종적으로 1개의 Bean만 조회되기 때문에 성공적으로 Bean 조회가 완료된다.

 

#2. @Qualifier

// 빈 등록시 @Qualifier로 추가적인 구분자를 붙인다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

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


// 자동 주입 예시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
                         @Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
     this.memberRepository = memberRepository;
     this.discountPolicy = discountPolicy;
}

 

@Qualifier는 스프링 Bean에 추가적인 구분자를 붙여주는 방법이다. @Qualifier는 주입 시 추가적인 방법을 제공하는 것뿐이지 빈 이름을 변경하는 것은 아니다. 의존관계를 주입할 때는 @Qualifier("구분자명")을 명시한다. 하지만, 이때 @Qualifier("구분자명")을 못 찾게 되면 어떻게 될까? @Qualifier("구분자명")으로 매칭이 실패하면 빈 이름으로 매칭을 시도한다. 빈 이름으로도 매칭이 실패하면 NoUniqueDefinitionException이 발생하게 된다.

#3. @Primary

 // @Primary가 우선권을 가진다.
 @Component
 @Primary
 public class RateDiscountPolicy implements DiscountPolicy {}
 
 @Component
 public class FixDiscountPolicy implements DiscountPolicy {}

 

 

@Primary는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary이 붙은 @Component가 우선권을 가지게 된다. 위 코드에서 만약, DiscountPolicy 타입으로 조회를 시도하면 RateDiscountPolicy와 FixDiscountPolicy가 매칭될 것이다. 여기서 RateDiscountPolicy에 @Primary가 붙어 있어 우선권을 가지게되므로 최종적으로 조회 결과는 RateDiscountPolicy가 된다.


3. @Primary, @Qualifier 그리고 둘의 우선순위

@Primary를 사용하게 되면, 생성자 코드를 따로 조작할 필요가 없게 되어 편리하다. 그렇기에 좀 더 자주 사용하는 Bean을 @Primary로 등록하고, 좀 더 특별할 때 사용되는 Bean을 @Qualifier로 등록하여 조회하는 것이 권장된다.

// Primary 등록
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{}

// @Qualifier("sub") 등록 
@Component
@Qualifier("sub")
public class FixDiscountPolicy implements DiscountPolicy{}

// 의존관계 주입
@Component
public class OrderServiceImpl implements OrderService{
    private final MemberService memberService;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberService memberService, 
    @Qualifier("sub") DiscountPolicy discountPolicy) {
        this.memberService = memberService;
        this.discountPolicy = discountPolicy;
    }
}

 

위 코드에서 RateDiscountPolicy는 @Primary, FixDiscountPolicy는 @Qualifier("sub")으로 등록되어 있다. 의존관계 주입 시에는 discountPolicy 앞에 @Qualifier("sub")가 붙어있다. 이때, DiscountPolicy 타입으로 스프링 빈을 조회한다면, 어떤 것이 조회될까? 스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다. 따라서 여기서도 @Qualifier가 우선권이 높다. 즉, @Primary와 @Qualifier가 사용되는 상황에서 @Qualifier으로 의존관계 주입을 시도한다면, @Qualifier가 우선권을 가져가게 된다. 물론, @Primary가 단독으로 사용되는 경우에는 @Primary가 우선권을 가져가게 된다.


4. 세줄 요약

1. 스프링 Bean을 조회할 때 기본적으로 타입을 이용한다. 이 때문에 조회 Bean이 2개 이상인 문제가 발생하기도 한다.

2. @Qualifier는 스프링 Bean에 구분자를 붙여 그 구분자로 스프링 Bean을 조회하는 방식이다.

3. @Primary는 스프링 조회 Bean이 2개 이상일 때, 어느 한 쪽에 우선권을 부여하여 Bean을 조회하는 방식이다.

 

스프링 핵심 원리 - 기본편 강의 - 인프런

스프링 입문자가 예제를 만들어가면서 스프링의 핵심 원리를 이해하고, 스프링 기본기를 확실히 다질 수 있습니다., 스프링 핵심 원리를 이해하고, 성장하는 백엔드 개발자가 되어보세요! 📢

www.inflearn.com

 

728x90