
기존에 RateDiscountPolicy만 스프링 빈으로 등록해두었는데, FixedDiscountPolicy까지 스프링 빈으로 등록해 두면 어떤 문제가 발생할까?
@Component // 애너테이션 추가
public class FixedDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; // 1000원 할인
...(생략)...
}
@Component
public class RateDiscountPolicy implements DiscountPolicy{
private int discountRate = 10;
...(생략)...
}


위와 같이 에러가 발생한다.
✅ 1. @Autowired 필드 명 매칭
✅ 2. @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
✅ 3. @Primary 사용
✅ 4. 애너테이션 직접 만들기
@Autowired 필드 명 매칭@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Override
public Order createOrder(Long id, String name, int price) {
Member member = memberRepository.findById(id);
int discount = discountPolicy.fixedDiscount(member, price);
return new Order(id, name, price, discount);
}
public MemberRepository getMemberRepository(){
return memberRepository;
}
}
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy rateDiscountPolicy;
@Override
public Order createOrder(Long id, String name, int price) {
Member member = memberRepository.findById(id);
int discount = rateDiscountPolicy.fixedDiscount(member, price);
return new Order(id, name, price, discount);
}
public MemberRepository getMemberRepository(){
return memberRepository;
}
}


전체 통과
@Autowired는 타입으로 우선 매칭 후 같은 타입이 두개라면 파라미터명으로 빈 이름을 매칭해준다.
따라서 구현체의 필드명을rate로 변경하게 되면 실제로 스프링 빈에 등록된DiscountPolicy타입의 빈이rate와fixed두개여도 필드명으로 조회하게 된다.
@Qualifier 붙이기@Component
@Qualifier("sub") // 애너테이션 추가
public class FixedDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; // 1000원 할인
...(생략)...
}
@Component
@Qualifier("main") // 애너테이션 추가
public class RateDiscountPolicy implements DiscountPolicy{
private int discountRate = 10;
...(생략)...
}
@Component
@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy rateDiscountPolicy;
@Override
public Order createOrder(Long id, String name, int price) {
Member member = memberRepository.findById(id);
int discount = rateDiscountPolicy.fixedDiscount(member, price);
return new Order(id, name, price, discount);
}
public MemberRepository getMemberRepository(){
return memberRepository;
}
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// @Required~ 애너테이션을
// 삭제 후 생성자를 다시 만들어 주었다.
// DiscountPolicy에 @Qualifier("main")를 붙여줬다
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("main") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}


만약에
@Qualifier로 주입 시@Qualifier("main")를 못 찾을 경우 추가로main라는 이름의 스프링 빈을 찾는다.
직접 빈 등록 시에도@Qualifier를 사용할 수 있다.@Bean @Qualifier("main") public DiscountPolicy discountPolicy(){ System.out.println("call Appconfig.discountPolicy"); return new RateDiscountPolicy(); }이 마저도 찾을 수 없게 된다면,
noSuchBeanDefinitionException을 발생시킨다.
<br>
@Primary 사용@Component
public class FixedDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; // 1000원 할인
...(생략)...
}
@Component
@Primary // 애너테이션 추가
public class RateDiscountPolicy implements DiscountPolicy{
private int discountRate = 10;
...(생략)...
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("main") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// `@Qualifier`를 삭제했다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}

@Primary는 우선권을 가진다. 빈에 빈에@Primary를 붙이게 될 경우 파라미터에는 별도로 붙일 애너테이션이 없다. 만약,@Primary와@Qualifier를 동시에 사용할 경우 우선권은@Qualifier가 가져간다.
<br>
package hello.core.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
// @Qualifier API에서 다음과 같은 애너테이션을 복사할 수 있다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Component
public class FixedDiscountPolicy implements DiscountPolicy{
private int discountFixAmount = 1000; // 1000원 할인
...(생략)...
}
@Component
// @Primary 애너테이션 삭제
@MainDiscountPolicy // 생성한 애너테이션 추가
public class RateDiscountPolicy implements DiscountPolicy{
private int discountRate = 10;
...(생략)...
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// `@Qualifier`를 삭제했다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
// @MainDiscountPolicy 를 추가했다
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}

@Qualifier("main")은 문자열이기 때문에 컴파일 시 타입체크가 되지 않는다. 그래서 애너테이션을 생성해서 문제를 해결할 수 있다. 애너테이션은 재정의가 가능하기 때문에@Autowired의 애너테이션도 재정의 할 수 있지만, 혼란을 가중할 수 있다.
타입매칭을 먼저 시도 함 -> 타입이 여러개일 경우 필드명, 파라미터명으로 매칭하게 된다.
추가 구분자를 붙여주는 방법. 구분자를 붙여주는 것이지, 빈이름을 변경하는 것이 아님
만약, 구분자를 못 찾을 경우 해당 이름으로된 스프링 빈을 추가로 찾게 된다.
우선순위를 정하는 방법. 파라미터에 별도로 애너테이션을 붙일 필요가 없고, 스프링 빈에만 붙여주면 된다.
@Qualifier 를 사용할 경우 문자열이라 컴파일 시 타입 체크를 할 수 없다. 이럴 때 애너테이션을 만들어서 해당 애너테이션을 추가로 넣어주면 된다.
애너테이션을 직접 만들땐 @Qualifier API에 있는 애너테이션을 모두 사용해야 한다.
import hello.core.AutoAppConfig;
import hello.core.Discount.DiscountPolicy;
import hello.core.member.Grade;
import hello.core.member.Member;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import java.util.List;
import java.util.Map;
public class AllBeanFind {
@Test
@DisplayName("모든 빈을 조회해야 할 때")
void allBean(){
// AutoAppConfig.class, DiscountService.class 모두 스프링 빈으로 등록한다.
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
// 스프링 빈으로 등록한 DiscountService.class를 조회한다.
DiscountService discountService = ac.getBean(DiscountService.class);
Member member1 = new Member(1L, "A", Grade.VIP);
Member member2 = new Member(2L, "B", Grade.VIP);
// DiscountService에 등록된 discount 사용
int fixedDiscountPolicy = discountService.discount(member1, 1000, "fixedDiscountPolicy");
int rateDiscountPolicy = discountService.discount(member2, 1000, "rateDiscountPolicy");
// 등록된 빈의 타입확인
Assertions.assertThat(discountService).isInstanceOf(DiscountService.class);
// 정액할인 정책 선택 시, 1000원 할인
Assertions.assertThat(fixedDiscountPolicy).isEqualTo(1000);
// 정률할인 정책 선택 시, 100원 할인
Assertions.assertThat(rateDiscountPolicy).isEqualTo(100);
}
static class DiscountService{
// 할인정책을 이름과 정책 map 으로 받는다.
private final Map<String, DiscountPolicy> policymap;
// 할인정책을 리스트로 받는다
private final List<DiscountPolicy> policies;
// 생성자
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);
}
public int discount(Member member, int price, String dicountcode){
DiscountPolicy discountPolicy = policymap.get(dicountcode);
System.out.println("dicountcode = " + dicountcode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.fixedDiscount(member, price);
}
}
}

map에 key값은 빈 이름으로 등록된다.
1. 자동 빈 등록이 좋은 경우
2. 수동 빈 등록이 좋은 경우
위의 상황처럼 만약, 정액할인과 정률할인 중 고객이 선택해서 사용해야 할 경우 위 테스트 코드처럼 만들게 되면 어떠한 할인 정책이 있는지 확인하기 어렵다. 다음과 같이 별도의 설정 정보를 만들고 수동으로 등록하면 좋다.
@Configuration
public class DiscountConfig {
@Bean
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixedDiscountPolicy(){
return new FixedDiscountPolicy();
}
}