반응형

조회 빈이 2개 이상 - 문제

@Autowired는 TYPE으로 조회한다.

@Autowired
private DiscountPolicy discountPolicy

하지만

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

해당 타입이 2개 스프링 빈으로 등록 되어 있다.....

그럼 과연 누굴 가져와야하느냐...

NoUniqueBeanDefinitionException 예외가 발생한다.

NoUniqueBeanDefinitionException: No qualifying bean of type
'hello.core.discount.DiscountPolicy' available: expected single matching bean
but found 2: fixDiscountPolicy,rateDiscountPolicy

보면 하나의 빈을 기대했는데 두개의 빈이 발견되었다고 알려준다.

 

이때 하위타입으로 지정할 수도 있다.

@Autowired
private FixDiscountPolicy discountPolicy

하지만 이러면 DIP를 위배하고 유연성이 떨어진다.

또한 이름만 다르고 완전히 똑같은 타입의 스프링 빈이 두개 있으면 해결이 안됨!

설정 구성에서 스프링 빈을 수동 등록해서 해결해도되지만, 의존관계 자동 주입에서 해결하는 여러 방법이있다.

 

 

@Autowired 필드 명, @Qualifier, @Primary

조회 대상 빈이 2개 이상인 경우엔

  • @Autowired 필드명
  • @Qualifier => @Qualfier끼리 매칭 => 빈 이름 매칭
  • @Primary 사용

 

@Autowired 필드명

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

@Autowired
private DiscountPolicy discountPolicy

위 코드에서 필드 이름을 바꾼다.

@Autowired
private DiscountPolicy rateDiscountPolicy

 

필드명 rateDiscountPolicy 이 정상 주입된다.

따라서 먼저 타입 매칭을 한 다음, 여러빈이 있을때 그중 같은 필드명을 가진 빈을 가져온다.

(여기선 fixDiscountPolicy, rateDiscountPolicy 두개가 있었으므로 DiscountPolicy 타입을 찾고 그중 rate를 가져온다.)

즉, 해당 타입 빈 찾음 => 여러개가 나오니까 필드명이름이랑 매칭되는 거 가져옴

 

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

DiscountPolicy중 mainDiscountPolicy를 가져온다고 알 수 있다.

(구현체에 의존하게 되므로 DIP 위반이지만 트레이드 오프가 있다 생각해야댐)

혹은 생정자 말고 수정자 자동 주입으로 사용하면

 

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

 

즉, 타입 앞에 @Qualifier("이름")을 붙여서 사용하면 된다.

만약 @Qualifier("mainDiscountPolicy")로 등록된 스프링 빈이 없으면?!?!

그러면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다.

하지만 @Qualifier는 스프링 빈이름을 찾는게 아니라 @Qualfier를 찾도록 하는 것이 좋다.

 

정리하면

@Qualifier끼리 매칭 => 없으면 빈이름 매칭 => 없으면 NoSuchBeanDefinitionException 예외 발생

 

 

@Primary 사용

@Primary는 우선 순위를 정하는 방법이다.

@Autowired 시에 여러 빈이 매칭되면 @Primary를 가져온다.

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

이러면 DiscountPolicy를 가져올때 RateDiscountPolicy가 우선권을 가지므로 Rate를 가져온다.

(따라서 @Qualifier와 같이 클라이언트 코드를 수정할 필요가 없다.)

그리고 또한 Qualifier의 단점은 모든 코드에 @Qualifier를 붙여야한다!!

 

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

등록할때와 그 스프링 빈을 사용할때 다 붙여야댐....

 

@Primary, @Qualfier 활용

예를 들어 코드에서

자주 사용하는 메인 DB의 커넥션을 획득하는 스프링 빈,

특별한 기능으로 가끔 사용하는 서브 DB의 커넥션을 획득하는 스프링 빈

이 있다고 가정.

 

메인DB 커넥션을 획득하는 스프링빈 => @Primary

서브 데이터베이스 커넥션을 획득하는 스프링 빈 => @Qualifier 사용하여 명시적 지정

과 같이 하면 깔끔해진다.

 

이때, 메인 DB의 스프링 빈을 등록할때 @Qualifier 를 지정해주는 것은 상관없다.

 

  • @Primary, @Qualfier 둘 중 우선순위

    스프링은 자동보다 수동이, 넓은 범위보단 좁은 범위가 우선순위가 높다.

    따라서 더 명시적인 @Qualifier가 우선권이 높다.

 

 

참고)

discountPolicy에 두 개의 빈이 찾아져버리므로, 특정 빈을 찾을 수 있도록 인자의 파라미터 이름을 수정해야했습니다. (@Autowired 필드명 방식)

이것이 개방-폐쇠 원칙을 못지킨 것이 아닌가 하는 의문이 들었습니다.

-> 네 맞습니다. 클라이언트 코드를 고쳐야 하기 때문에 OCP를 지키기 못했습니다.



@Quilifier 혹은 @Primary 어노테이션을 붙이기 위해 구현체의 클래스를 찾아가서 수정해줘야하는 것 같습니다.

-> 기존 구현 클래스의 애노테이션도 변경하지 않으면 더 좋겠지만, 이 부분까지는 컴포넌트 스캔의 한계입니다. @Bean을 사용하면 확실하게 되지만 약간은 불편하지요. 따라서 둘의 트레이드 오프로 이해하시면 됩니다.

 

어노테이션 직접 만들기

@Qualifier 사용시 만약

@Qualifier("mainDiscountPolicy")가 아니라

@Qualifier("mmainDiscountPolicy") 처럼 오타가 났다면?!?!?!

문자를 적으면 컴파일시 타입 체크가 되지 않는다. => 오류 찾기 힘들다...

따라서 직접 어노테이션을 만들어서 컴파일시 체크되게 한다.

package hello.core.annotation;


import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

//ctrl+n 으로 Qualifier 어노테이션꺼 다 긁어와주면댐
//즉 @Qualifier 설정 다 가져옴
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy") //적어줌 여기서
public @interface MainDiscountPolicy {
}

그러면 기존에 사용했던 방식을 보자

package hello.core.discount;

import hello.core.member.Grade;
import hello.core.member.Member;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

//일단 Fix할인 정책에서 Rate할인정책으로 바뀌였으니 비율할인 정책 구현체를 빈으로 등록
@Component
//10퍼센트만 할인 한다고 가정
@Qualifier("mainDiscountPolicy") //이렇게 직접 문자로  적어줬다. <==== 따라서 오타가 나면 찾기 힘들다..
public class RateDiscountPolicy implements DiscountPolicy{
    //할인율
    private int discountPrice = 10;

    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){
            return price*discountPrice/100;
        }
        else{
            return 0;
        }
    }
}

 

하지만 만든 어노테이션을 적용하면

package hello.core.discount;

import hello.core.annotation.MainDiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

//일단 Fix할인 정책에서 Rate할인정책으로 바뀌였으니 비율할인 정책 구현체를 빈으로 등록
@Component
//10퍼센트만 할인 한다고 가정
//@Qualifier("mainDiscountPolicy")
@MainDiscountPolicy // <========================= 요ㅕ기 수정
public class RateDiscountPolicy implements DiscountPolicy{
    //할인율
    private int discountPrice = 10;

    @Override
    public int discount(Member member, int price) {
        if(member.getGrade() == Grade.VIP){
            return price*discountPrice/100;
        }
        else{
            return 0;
        }
    }
}

@MainDiscountPolicy 라고 붙여주므로 오타가 나도 컴파일 시점에서 잡아주니까 찾기 쉽다!!

신! 난! 다!

 

또한 DiscountPolicy를 사용할 때 보면

//생성자 자동 주입
@Autowired
//@MainDiscountPolicy 사용
public OrderServiceImpl(MemberRepository memberRepository,@MainDiscountPolicy DiscountPolicy discountPolicy) {
 	this.memberRepository = memberRepository;
 	this.discountPolicy = discountPolicy;
}


//수정자 자동 주입
@Autowired
//@MainDiscountPolicy 사용
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
 	return discountPolicy;
}

 

사실 어노테이션에는 상속이라는 개념이 없다

그러면 어떻게 MainDiscountPolicy 어노테이션이 @Qualifier와 같은 역할을 하게 되었을까...

바로 스프링이 제공해주는 기능이다. (갓갓...)

따라서 @Qualifier뿐만 아니라 다른 어노테이션들도 함께 조합해서 사용 가능하다.

(@Autowired도 재정의 가능하다..)

킹치만,,, 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분멸하게 재정의하면 유지보수에 혼란을 준다..

 

728x90
반응형
블로그 이미지

아상관없어

,