같은 타입의 빈을 구분하기위한 @Qualifier 어노테이션
Table of Content
구현체가 2개인 상황
DiscountPolicy는 고정 할인인 FixDiscountPolicy, 비율 할인인 RateDiscountPolicy 처럼 구현체가 2개가 있으며 계획에 따라 선택하여 사용하고 있다.
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;
@Component
public class FixDiscountPolicy implements DiscountPolicy {
private int discountFixAmout = 1000; // 1000원 기본 할인
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return discountFixAmout;
} else {
return 0;
}
}
}
Java
복사
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;
@Component
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
Java
복사
하지만 이처럼 두 클래스 모두 @Component를 명시하게 되면 @ComponentScan에 의해 모두 스프링 빈으로 등록되면서 추상 클래스 DiscountPolicy를 호출하는 단계에서 스프링은 어떤 것을 선택 할 지 알 수 없게 된다.
여기서 서비스 코드에 직접 Fix 또는 Rate를 명시하면 DIP를 위반하면서 최상위 부모 또는 추상화를 주입해야되는 설계 원칙을 위반하게 된다.
@Qualifier 어노테이션으로 구분하기
각 구현체 클래스에 어노테이션으로 명시적으로 이름을 구분 할 수 있다.
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {
private int discountFixAmout = 1000; // 1000원 기본 할인
...
Java
복사
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
...
Java
복사
다음처럼 어노테이션으로 이름을 지정해준다. 구현체를 호출하는 부분에서도 @Qualifier 를 통해 호출하고자 하는 빈을 특정하게 된다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
Java
복사
이러면 어노테이션으로 mainDiscountPolicy 이름으로 등록된 빈을 추가로 스캔하면서 RateDiscountPolicy 객체를 지정하게 된다.
이처럼 명확하게 특정 빈을 찾는 용도로만 사용하는 것이 좋다.
@Primary로 여러 구현체에서 기 본값
DiscountPolicy 구현체 중에서 RateDiscountPolicy가 선택되도록 하려면 @Primary 어노테이션을 작성하면 된다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
...
Java
복사
@Component
public class FixDiscountPolicy implements DiscountPolicy {
private int discountFixAmout = 1000; // 1000원 기본 할인
...
Java
복사
우선권
이처럼 @Primary는 마치 default 처럼 등록하는 개념이며 @Qualifier는 커스텀하여 지정하는 개념이다.
따라서 @Qualifier가 더 상세하므로 우선순위가 높다 > @Primary
애너테이션 만들기
@Qualifier로 특정 이름을 구분하는 것은 컴파일 단계에서의 문자열 오기입을 찾을 수 없다.(ex: mainnDiscounttPolicy 처럼 잘못 기입하는 사례 등)
따라서 어노테이션을 직접 만들어서 사용하는 방법이 있다.
우선 MainDiscountPolicy라는 인터페이스를 만들어 다음과 같이 작성하여 애너테이션임을 작성
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
Java
복사
해당 애너테이션이 적용될 구현체 클래스에 애너테이션을 추가 작성한다.
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {
private int discountPercent = 10;
@Override
public int discount(Member member, int price) {
if (member.getGrade() == Grade.VIP) {
return price * discountPercent / 100;
} else {
return 0;
}
}
}
Java
복사
이제 RateDiscountPolicy가 @MainDiscountPolicy 애너테이션으로 적용되었으며 해당 구현체를 호출하는 곳에서도 동일한 애너테이션을 작성해서 호출하여 특정 구현체를 명시적으로 지목하게 된다.
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
...
Java
복사
Related Posts
Search