관리 메뉴

공부 기록장 💻

[Spring] 조회 빈이 2개 이상인 경우의 문제를 해결해보자 (필드명, @Qualifier, @Primary) 본문

# Tech Studies/Java Spring • Boot

[Spring] 조회 빈이 2개 이상인 경우의 문제를 해결해보자 (필드명, @Qualifier, @Primary)

dream_for 2023. 2. 9. 10:38

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


 

조회되는 스프링 빈이 2개 이상일 때, 문제가 발생한다. (NoUniqueBeanDefinitionException

 

@Autowired 는 기본적으로 타입(Type)으로 조회한다.

타입으로 조회하기 때문에, DiscountPolicy 타입에 대하여

@Autowired
private DiscoutPolicy discountPolicy
ac.getBean(DiscountPolicy.class) 와 유사하게 동작한다고 볼 수 있다.

 

이전에 스프링 빈 조회 시 선택된 빈이 2개 이상일 때 문제가 발생함을 배웠다.

아래 예시로, DiscountPolicy를 구현하는 RatedDiscountPolicy, FixedDiscountPolicy에 모두 @Component를 붙여 두 구현 객체 모두 스프링 빈으로 선언해보자.

 

 

이후 OrderService 구현 객체에서 DiscountPolicy에 대해 의존 관계 자동 주입을 설정하면,

아래와 같이 UnsatisfiedDependencyExceptionNoUniqueBeanDefinitionException 오류가 발생한다.

 

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'orderServiceImpl' defined in file [..]: Unsatisfied dependency expressed through constructor parameter 1: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixedDiscountPolicy,ratedDiscountPolicy
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixedDiscountPolicy,ratedDiscountPolicy at org.springframe

 

expected single matching bean but found 2: fixedDiscountPolicy, ratedDiscountPolicy, 즉 두 개의 빈이 발견되어 발생한 문제이다.

 

 

이 문제를 해결하기 위해 아래와 같이 하위 타입으로 지정하는 것은 DIP를 위배하며, 유연성이 떨어지는 방법이다.

 

 

결국 이름만 다르면서, 완전히 동일한 타입의 스프링 빈이 2개 있을 때는 해결이 어렵다.

스프링 빈을 수동 등록하는 방법말고, 의존 관계 자동 주입의 관점에서 해결하는 여러 방법을 알아보자.

 

 


 

 

여러 개의 빈이 선택될 때 해결하는 방법 3가지를 알아보자.

1. @Autoriwred 필드명/파라미터명으로 매칭하기
2. @Qualifier@Qualifier 끼리 매칭하여 빈 이름 매칭하기
3. @Primary 사용하기

 

 

 

 

1. @Autowired의 필드 명 매칭

@Autowired 타입 매칭을 시도하고, 이 때 여러 빈이 조회되면 필드 명, 파라미터 명으로 빈 이름을 추가 매칭을 시도한다.

 

파라미터 이름

다음과 같이 DiscountPolicy 타입에 대해, 생성자의 파라미터명이 ratedDiscountPolicy로 지정한 경우

파라미터 명과 동일한 RatedDiscountPolicy 객체를 선택하게 된다.

 

 

필드명

필드 주입을 통한 의존 관계 주입 시에는 다음과 같이 필드 명을 하위 클래스 객체의 이름으로 설정해줄 수 있다.

 

 

 

 

2. @Qualifier 사용

@Qualifier 는 추가 구분자를 붙여주는 방법이다. 주입 시 추가적인 방법을 제공하는 것 뿐이지, 빈 이름을 변경하는 것은 아니다.

 

다음과 같이 각 구현 객체에 @Qualifier("빈 별명") 을 추가해주고, 

 

 

생성자 파라미터에도 @Qualifier 애노테이션을 추가해하면 된다.

이제 mainDiscountPolicy라는 이름을 @Qualifier 애노테이션으로 등록한 스프링 빈이 조회가 될 것이다.

 

 

아래와 같이 setter의 경우에도 @Qualifier로 이름을 추가적으로 등록해 줄 수 있다.

 

 

 

 

다음과 같이 @Bean 을 이용해 스프링 빈을 등록할 때 다음과 같이 구분자를 추가적으로 지정해줄 수도 있다.

 


그런데 만약 @Qualifier로 설정한 이름의 스프링 빈을 찾지 못한다면 어떻게 될까?

mainDiscountPolicy라는 이름의 스프링 빈이 있는지 추가적으로 찾게 된다. 그럼에도 찾지 못한 경우 NoSuchBeanDefinitionException이 발생한다.

그렇지만, @Qualifier은 스프링 빈 이름으로 탐색하기 위한 용도로 사용하지 말고, @Qualifier을 찾는 용도로만 사용하는 것이 명확하다.

 

 

 

3. @Primary로 조회할 빈의 우선순위 부여하기

@Primary를 적용하여 해당 스프링 빈을 조회의 최상위 우선순위를 부여하는 방법이다.

 

이전의 @Qualifier을 모두 지우고, RatedDiscountPolicy에 @Primary를 적용하면,

DiscountPolicy 를 조회할 때, RatedDiscountPolicy 빈을 참조하게 된다.

 

 


 

 

@Primary vs @Qualifier, 우선순위는?

 

@Primary는 우선순위를 부여할 스프링 빈에만 적용해주면 되기 때문에 훨씬 간단한 반면, @Qualifer의 경우 주입 받아야 하는 위치의 모든 코드에 @Qualifier 을 붙여줘야 한다.

@Qualifier의 경우 각 스프링 빈의 구분자를 상세하게 부여하게 되므로  매우 좁은 범위의 선택권을 가져갈 수 있으므로 @Primary에 비해 우선순위가 높다고 볼 수 있다. 

(스프링은 자동보다는 수동이, 넓은 범위의 선택권 보다는 좁은 범위의 선택권이 높은 우선순위를 가져간다.)

 

예시로,

메인 데이터베이스와 서브 데이터베이스의 커넥션을 획득하는 스프링 빈이 있다고 가정할 때,

따라서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary를 적용하고, 특별한 기능으로 가끔 사용하게 될 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier을 지정하여 명시적으로 해당 스프링 빈을 획득하는 방식으로 구현하면 코드를 깔끔하게 유지할 수 있다.

 

728x90
반응형
Comments