jhhan의 블로그

스프링 코어(12) - 조회 빈이 여러 개인 경우 본문

Spring

스프링 코어(12) - 조회 빈이 여러 개인 경우

jhhan000 2021. 7. 28. 21:56

이번 포스트에서는 제목처럼 2개 이상의 빈이 있을 때 어떻게 처리하는지 알아보겠습니다.

 

 

원래 스프링 빈은 하나가 등록되어야 합니다.

하지만 인간이기에 스프링 빈을 2개 이상 등록하는 실수를 할 수 있습니다.

그러면 에러가 발생하겠죠..

이런 에러가 발생하는 것을 막는 법에 대해 알아보겠습니다.

 

일단 먼저 스프링 빈이 2개 등록되었을 경우를 알아보겠습니다.

저는 DiscountPolicy 인터페이스로 2개의 구현체를 만들었습니다.

그 중 RateDiscountPolicy에만 @Component 애노테이션이 있었기 때문에

OrderServiceImpl을 실행하는데 문제가 없었습니다.

근데 FixDiscountPolicy에도 애노테이션을 넣는다면 어떻게 될까요?

FixDiscountPolicy에도 추가를 한 다음 테스트를 돌려봅니다.

그러면 이전에 만들어둔 AutoAppConfigTest에서 에러가 생기는 것을 확인할 수 있습니다.

에러 내용은

"expected single matching bean but found 2" 라는 문구를 볼 수 있습니다.

한 개의 빈을 예상했지만 2개를 발견했다고 합니다.

 

@Component를 두군데에 넣어놨기 때문에 이런 에러가 발생했습니다.

애노테이션을 추가하더라도 코드는 문제없이 잘 돌아가야 합니다.

그러면 문제없이 돌아가도록 해결책을 알아보겠습니다.

 

1. 필드명 매칭

파라미터 이름을 빈 이름으로 진행합니다.

설명이 애매하므로 바로 코드에 적용해 보겠습니다.

OrderServiceImpl 클래스를 다음과 같이 수정합니다.

@Component
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

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

실행한다면 아까 에러가 발생했던 AutoAppConfigTest 클래스에서는 더 이상 에러가 발생하지 않는다는 것을 알 수 있습니다.

필드명 매칭은 먼저 빈 타입 매칭을 진행한 후 여러 개가 검색되면 추가로 동작되는 기능입니다.

  • 빈 타입 매칭
  • 여러 개의 빈 타입 매칭 → 필드명(파라미터 명)으로 빈 이름 매칭

필드명 매칭을 진행하려면 기존에 선언했던 클래스와 이름을 아주 비슷하게 해야합니다.

  • RateDiscountPolicy → rateDiscountPolicy
  • FixDiscountPolicy → fixDiscountPolicy

 

cf) 참고로 필드명 주입으로 전체 테스트 코드를 실행한다면 AutoAppConfigTest는 통과하지만,

    다른 코드에서는 에러가 납니다. ( UnsatisfiedDependencyException )

    왜냐하면, 파라미터 명이 바뀌어서 빈을 제대로 못 찾기 때문입니다... (아마도...)

 

2. @Qualifier

Qualifier라는 추가 구분자를 붙여주는 방법입니다.

단, 빈 이름을 변경하는 방법은 아닙니다.

각 클래스에 다음과 같은 @Qualifier를 붙여줍니다.

@Component
public class OrderServiceImpl implements OrderService{

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
        System.out.println("Constructor: memberRepository = " + memberRepository);
        System.out.println("Constructor: discountPolicy = " + discountPolicy);
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
	...
}

그리고 OrderServiceImpl를 다음과 같이 수정합니다.

코드 수정 후 테스트를 돌려보면 에러가 발생하지 않은 것을 확인할 수 있습니다.

cf) 그리고 Qualifier는 생성자 주입과 수정자 주입에서 모두 사용이 가능합니다.

 

@Qualifier를 사용하면 옆에 설정한 이름을 찾아서(여기서는 mainDiscountPolicy) 주입합니다.

만약 mainDiscountPolicy를 못 찾으면..

mainDiscountPolicy라는 이름을 갖는 스프링 빈을 추가로 찾습니다.

하지만, 왠만하면 설정한 이름을 찾게 코드를 짜는 것이 이롭다고 생각합니다.

 

@Qualifier 정리

  • Qualifier 끼리 매칭
  • 다음으로 빈 이름 매칭
  • 둘 다 없으면 NoSuchBeanDefinitionException

 

3. Primary

@Primary를 사용하고 이 방법은 우선순위를 정하는 것과 비슷합니다.

여러 개의 빈이 검색되는 경우 @Primary가 붙어있는 것이 우선적으로 사용됩니다.

코드로 바로 살펴보겠습니다.

2개의 클래스를 위와 같이 변경합니다.

그리고 OrderServiceImpl을 위와 같이 변경합니다.

테스트를 돌려보면.. 오류없이 모두 통과하는 것을 알 수 있습니다.

 

@Primary 정리

  • 빈 여러 개 검색 시
  • @Primary가 붙은 녀석이 사용된다.

 

 

 

이렇게 3가지를 살펴보았고,

3개 중에서 Primary를 사용하는 것이 가장 편한 것을 알 수 있습니다.

사용 측면에서는 Primary와 Qualifier가 많이 사용된다고 하네요.

 

@Qualifier를 사용하게 되면 @Primary와 달리 더 많은 코드를 작성해야 하고,

모든 코드에 @Qualifier를 붙여야 하는 단점도 있습니다.

그래도 Qualifier를 사용하는 이유가 있겠죠 ㅎㅎ

 

@Primary는 우선권만 부여하는 방식이기 때문에 동작 방식이 간단합니다.

@Qualifier는 코드를 많이 작성하는 대신 자세하게 동작하는 방식을 설정할 수 있습니다.

보통 더 자세하게 조작할 수 있는 방식이 먼저 사용되는 편입니다.

→ @Primary와 @Qualifier를 같이 사용하게 된다면 @Qualifier가 사용된 쪽이 우선권이 높다는 것을 알고 있으면 될 것 같습니다.

@Primary와 @Qualifier를 적당히 잘 사용하면 되겠네요!

 

 

 

이렇게 조회한 빈이 2개 이상인 경우 해결방법에 대해 알아봤습니다.

실제로 2개 이상의 빈을 설정하는 실수는 흔히 할 수 있을 것 같기 때문에

알아놓으면 코드 에러가 발생하는 확률을 많이 낮춰줄 것 같습니다~

 

 

 

 

출처: 인프런 - 스프링 핵심원리(기본편) by 김영한