Spring(8) - 주입될 빈이 2개 이상 조회된다? + 빈 모두 불러오기

김유담·2024년 6월 20일

spring

목록 보기
8/11
post-thumbnail

김영한 강사님스프링 핵심 원리의 강의 내용과 자료를 이용했음을 밝힙니다.

👨‍💻 빈이 여러개 조회될 때

스프링에서 @Autowired를 통해 자동주입을 할 때, 타입으로 조회한다.

그러면 여기서 드는 의문!

"만약 같은 Type이 2개 이상이면 어느걸 주입할까?"

결과는 NoUniqueBeanDefinitionException이 발생한다.

그러면 같은 타입이 2개 이상이면 들어갈 빈을 지정해줘야겠다.

  • @Autowired 필드 명 매칭
  • @Qulifier
  • @Primary

위 3가지 방법으로 지정이 가능하다.
참고로 @Primary 방법을 사용하는게 가장 깔끔하고 편하다.(혹시 앞에 방법만 보고 돌아갈까봐.. )

👨‍💻 @Autowired 필드 명 매칭

이 방식은 빈을 선택하는 방식을 추가하는 것인데
기본적으로 타입 매칭을 시도하고 주입될 수 있는 빈이 복수개이면 필드 이름, 파라미터 이름과 같은 빈을 찾아서 넣는 것이다.

예시를 보면

@Autowired
private DiscountPolicy rateDiscountPolicy;

이렇게 하면 DiscountPolicy 타입이 여러개더라도 그중에 rateDiscountPolicy라는 이름의 빈을 찾아와서 주입을 한다.

private DiscountPolicy discountPolicy;

@Autowired
public constructor(rateDiscountPolicy){//생성자
	this.discountPolicy = rateDiscountPolicy;
}

이런식으로 생성자의 파라미터 이름으로도 찾는다.

필드 명, 파라미터 명 중 하나만 이름 맞춰줘도 잘 찾아주니 다 바꿔야 할지 걱정은 안해도 된다.

그런데 이렇게 했는데도 빈이 복수개여서 나타난 오류가 안해결 되는 경우가 있다.

그런 경우에는 위에서 말한 NoUniqueBeanDefinitionException이 아니라 UnsatisfiedDependencyException이 발생해서 그렇다.

UnsatisfiedDependencyException은 @Autowired 필드 명 매칭으로는 안되니 밑의 2개의 방법으로 해결하면 된다.

👨‍💻 @Qualifier

이건 위에서 본 @Autowired 방법이랑은 다르게 특정 빈을 아예 지정해버리는 방식이다.

우선 등록하고 싶은 빈에다가 @Qualifier랑 내가 사용하고 싶은 일종의 key를 적는다.

@Component
@Qualifier("mainDiscountPolicy")
	public class RateDiscountPolicy{}

그리고 자동 주입하는 곳에도 이 Qualifier를 사용한다.

@Autowired
public constructor(@Qualifier("mainDiscountPolicy") rateDiscountPolicy){//생성자
	this.discountPolicy = rateDiscountPolicy;
}

이런 식으로 똑같이 넣어주면 같은 Qualifier 값(key 값)을 가지고 있는 빈을 가져온다.

추가로 말하자면 혹시 @Qualifier("mainDiscountPolicy")를 못찾으면 mainDiscountPolicy라는 이름의 빈을 찾는다.

하지만 괜히 남이 이해하기 어렵게 만들지 말고 그냥 핵심 기능에 맞게 사용하자!

👨‍💻 @Primary

그냥 @Primary 붙은 얘가 우선순위를 가져간다.

@Component
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

이렇게 같은 타입의 빈 두 개가 있다면

@Component
@Primary //check here!!
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
public class FixDiscountPolicy implements DiscountPolicy {}

이렇게 @Primary 붙은 빈이 다른 빈 보다 우선순위를 가져서 주입이 된다.

👨‍💻 @Primary VS @Qualifier

그러면 둘 다 사용하면 누가 이길까?

@Component
@Primary 
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
@Qualifier("mainDiscountPolicy")//생성자 주입의 생성자에 똑같은 Qualifier 붙였다고 가정
public class FixDiscountPolicy implements DiscountPolicy {}

늘 그렇듯이 좀 더 자세하게 규정한 것이 이긴다 -> @Qualifier 붙은 빈이 주입된다.

👨‍💻 애노테이션을 직접 만들기

사실 한가지 방법이 더 있다.
직접 애노테이션을 만들면 되는데

새 창(?) 만들 때, class, interface, enum 이런 목록 중에 annotation이 있을 것이다.
그걸 만들면 끝이다.

강의에서 만든 애노테이션을 보자면

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

이렇게 만들었고 이 애노테이션 위에 있는 Target~Qualifier까지 모든 기능이 이 @MainDiscountPolicy에 들어가게 된다.

즉 @Qualifier("mainDiscountPolicy")가 있으니 @Qualifier("mainDiscountPolicy") 대신해서 @MainDiscountPolicy를 사용할 수 있는 것이다.

@Component
@Qualifier("mainDiscountPolicy")
	public class RateDiscountPolicy{}
@Component
@MainDiscountPolicy
	public class RateDiscountPolicy{}

두 개가 same ~ same

근데 @Qualifier가 있는데 굳이 이렇게 애노테이션을 만드는 것은 유지보수에 혼란을 더할 수 있으니 정말 필요할 때만 하자.

👨‍💻 조회한 빈이 모두 필요할 때

사실 이건 좀 다른 이야기이긴 한데 새로운 글에서 말하기에는 너무 간단해서 여기에 살짝 넣어서 말하겠다.

private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;

@Autowired
public DiscountService(Map<String, DiscountPolicy> policyMap, List<DiscountPolicy> policies) {
    this.policyMap = policyMap;
    this.policies = policies;
    System.out.println("policyMap = " + policyMap);
    System.out.println("policies = " + policies);
}

이런 식으로 하면 policyMap와 policies에 타입이 맞는 모든 빈들이 저장된다.

이걸 이용해서 클라이언트에서 빈을 선택하게 해서 파라미터로 받은 뒤, Map에서 찾아서 알맞은 기능을 처리하게 할 수도 있다.

이런 식으로!

public int discount(Member member, int price, String discountCode) {
   DiscountPolicy discountPolicy = policyMap.get(discountCode);
   return discountPolicy.discount(member, price);
}// 

클라이언트가 discount 메소드를 이용해 원하는 빈 이름을 discountCode 파라미터로 보낼 수 있다.

profile
잘하진 못할지언정 꾸준히 하는 개발자:)

0개의 댓글