[스프링(Spring)] @Autowired 조회 대상 빈이 여러 개일때는 어떻게 할까? (feat.NoUniqueBeanDefinitionException)

kindof

·

2021. 8. 30. 13:34

📒 들어가면서 

의존관계 주입을 할 때 사용하는 @Autowired 어노테이션은 기본적으로 스프링 빈을 타입(Type)으로 조회합니다.

 

예를 들어 아래처럼 할인정책에 관한 내용을 담고 있는 discountPolicy에 대한 의존관계를 주입한다고 해봅시다.

@Autowired
private DiscountPolicy discountPolicy

이 때, discountPolicy 인터페이스에는 각각 다른 할인 정책을 구현한 여러가지 구현체들이 있을 것입니다(discountPolicy1, discountPolicy2,...,discountPolicyN).

 

만약 이 상황에서 아래와 같이 DiscountPolicy의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy를 둘 다 스프링 빈으로 선언하고,

@Component
public class FixDiscountPolicy implements DiscountPolicy{}
@Component
public class RateDiscountPolicy implements DiscountPolicy{}

discountPolicy에 대한 의존관계를 주입하려고 하면 어떻게 될까요?

 

"어떤 스프링 빈을 주입할 지 알 수 없기 때문에" NoUniqueBeanDefinitionException 컴파일 에러가 발생합니다.

NoUniqueBeanDefinitionException

 

이를 해결하기 위해 어떤 시점에 필요한 스프링 빈만을 컴포넌트로 지정하고 이를 바꿔가면서 코드를 수정하거나 스프링 빈을 수동으로 등록해서 할 수도 있겠지만, 이는 DIP를 위배하고 유연성이 떨어질 수밖에 없습니다.

 

따라서 이러한 문제점을 어떻게 해결할 것인가에 대한 방법론들에 대해 정리해보려고 합니다.

 

 

✅ 1. @Autowired 필드명 매칭

@Autowired는 타입 매칭을 시도하고, 이 때 여러 빈이 있으면 필드 이름이나 파라미터 이름으로 빈 이름을 추가 매칭합니다.

@Autowired
private DiscountPolicy discountPolicy

 

따라서 위 의존관계 주입에서는 discountPolicy타입의 스프링 빈이 FixDiscountPolicy, RateDiscountPolicy가 존재하기 때문에 컴파일 오류가 났으므로 이를 아래처럼 구체적인 스프링 빈 이름으로 추가 매칭해줌으로써 해결할 수 있는 것입니다.

...
@Autowired
private DiscountPolicy rateDiscountPolicy
...

 

 

조금 더 구체적으로 예를 들어보겠습니다. 아래처럼 생성자 주입을 통해 의존관계를 주입한다고 해봅시다.

@Component
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

 

생성자 주입에서 파라미터로 memberRepository, discountPolicy를 받고 있는데 만약 여기서 RateDiscountPolicy를 할인 정책으로 사용하고 싶다면 생성자의 파라미터를 구체적인 스프링 빈 이름으로 지정해주면 됩니다.

...
@Autowired
pulic OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy RateDiscountPolicy)
...

 

* 지금은 원리를 설명하기 때문에 위와 같이 생성자 주입을 다 써주고 있는데, 실제로 제가 개발할 때는 @RequiredArgsConstructor로 어노테이션 처리하고, 필드명 매칭을 통해 생성자 주입할 대상을 명시해줍니다.

 

✅  2. @Qualifier 빈 이름 매칭

@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;
}

 

수정자 주입 역시 아래처럼 할 수 있죠.

@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
    return discountPolicy;
}

 

@Qualifier는 해당 어노테이션을 달고 있는 스프링 빈을 정확하게 매칭시켜 주지만, 의존 관계를 주입받을 때 모든 코드에 @Qualifier를 붙여주어야 한다는 단점이 있습니다.

 

✅  3. @Primary

@Primary는 스프링 빈에 우선순위를 정하는 방법입니다. @Autowired를 사용할 때 여러 빈이 매칭되면 @Primary가 우선권을 가지게 됩니다.

 

예를 들어 RateDiscountPolicy를 할인 정책 중에서 우선순위를 갖는 스프링 빈으로 등록하려면 아래처럼 코드를 쓸 수 있습니다.

@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{}


@Component
public class FixDiscountPolicy implements DiscountPolicy{}
...

 

이렇게 우선순위를 갖게 된 스프링 빈에 대해 의존관계를 주입할 때는 별도의 다른 코드를 작성할 필요가 없습니다. 아래처럼 생성자 주입을 바로 해줄 수 있죠.

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

 

discountPolicy 하위 타입의 스프링 빈은 RateDiscountPolicy, FixDiscountPolicy가 존재하지만 RateDiscountPolicy에 우선 순위가 있기 때문에 자동으로 RateDiscountPolicy에 대한 의존관계를 주입해주는 것입니다.

 

 

📋 4. @Qualifier와 @Primary의 우선순위

지금까지 살펴본 내용에 따르면 @Primary는 기본값으로 설정할 스프링 빈을 정해준다는 느낌이고, @Qualifier는 의존관계를 주입할 때 정확히 필드명이 일치하는 스프링 빈을 정해주는 느낌을 받을 수 있습니다.

 

따라서, 만약 @Primary를 통해 우선순위를 갖는 스프링 빈을 지정해준다고 하더라도 @Qualifier를 통해 매칭이 되는 스프링 빈에 대한 의존관계를 주입한다면 @Qualifier가 더 높은 우선순위를 갖게 됩니다.

 

 

🚗 5. 나가면서

이번 포스팅에서는 @Autowired 조회 대상 빈이 여러 개일때는 어떻게 할 것인가에 대한 방법론들에 대해 공부해보았습니다.

 

어려운 내용은 아니라고 생각하는데, 사실 맨 처음에 의문을 품었던 수동으로 빈을 등록하는 행위나 Primary, Qualifier를 통해 빈을 등록하는 행위가 객체 지향적인 관점에서 어떤 큰 차이를 갖는지 와닿지는 않는 것 같습니다.

 

조금 더 공부를 해보고 실제로 써보면서 그 차이에 대해 고민해봐야겠습니다.