1. 조회 빈이 2개 이상인 경우
@Autowired를 통해 의존 관계를 주입할 때 타입으로 조회하여 주입하게 된다. 만약 같은 타입의 빈이 2개 이상일 때 에러가 발생한다.
@Component
public class FixDiscountPolicy implements DiscountPolicy {...}
@Component
public class RateDiscountPolicy implements DiscountPolicy {...}
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
- OrderServiceImpl에서 의존 관계를 주입 받을 때 discountPolicy 타입의 빈을 조회하는데 FixDiscountPolicy, RateDiscountPolicy 두 개의 빈이 존재하기 때문에 아래와 같은 에러 메시지가 발생한다.
2. 조회 빈이 여러개 일 경우 해결 방법
조회 빈이 여러개일 경우 해결하는 4가지 방법에 대해서 정리해보자.
a. @Autowired 파라미터 이름 매칭
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
}
- @Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
- 파라미터 이름을 rateDiscountPolicy로 변경하여 문제를 해결할 수 있다.
b. @Qualifier @Qualifier끼리 매칭 빈 이름 매칭
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {...}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {...}
@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 = rateDiscountPolicy;
}
}
- @Qualifier는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.
- @Qualifier로 주입할 때 @Qualifier("mainDiscountPolicy") 찾지 못할 경우, mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 빈을 결국 찾지 못할 경우 NoSuchBeanDefinitionException 예외 발생한다.
c. 어노테이션 직접 만들어서 사용
@Qualifier 내부 코드로 들어가면 이런식으로 구현되어 있다.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Qualifier {...}
이걸 참고해서 직접 어노테이션을 만들어보자.
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {...}
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DiscountPolicy {...}
@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 = rateDiscountPolicy;
}
}
- 어노테이션을 직접 만들어서 사용하면 컴파일 시점에서 타입을 체크할 수 있기 때문에 오타와 같은 실수를 줄여줄 수 있고, 유지보수에 용이하다.
d. Primary 사용
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {...}
@Component
public class FixDiscountPolicy implements DiscountPolicy {...}
@Component
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
}
- @Primary를 통해 우선순위를 정할 수 있다.
- @Primary 붙은 RateDiscountPolicy가 우선권을 가지게 되어있다.
3. 조회한 빈이 모두 필요한 경우
지금까지는 여러 개의 빈 중에 하나의 빈만 주입해서 사용하는 것을 정리했다. 하지만 두 개의 빈 모두 필요한 경우를 생각해보자.
예를 들어, 상황에 따라 할인 종류를 다르게 선택한다고 가정하자. 코드를 통해 보여주겠다.
@Test
void findAllBean() {
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "userA", Grade.VIP);
// member가 10000d원 구매하고 할인받는 금액(정액할인정책)
int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
assertThat(discountService).isInstanceOf(DiscountService.class);
assertThat(discountPrice).isEqualTo(1000);
// member가 20000원을 구매하고 할인받는 금액(정률할인정책)
int rateDiscountPrice = discountService.discount(member, 20000, "rateDiscountPolicy");
assertThat(rateDiscountPrice).isEqualTo(2000);
}
- userA가 10000원만큼 주문했을 경우에 정액할인정책(fixDiscountPolicy)을 적용하고, userA가 20000원만큼 주문했을 때 정률할인정책(rateDiscountPolicy)를 적용한다고 가정하자.
class DiscountService {
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 discountCode) {
DiscountPolicy discountPolicy = policyMap.get(discountCode);
return discountPolicy.discount(member, price);
}
}
- 필드의 Map 또는 리스트에 빈을 담을 것이다. 생성자를 통해 의존 관계를 주입할 경우 아래와 같이 빈이 저장된다.
- Map 또는 리스트에 담긴 할인정책(DiscountPolicy)을 상황에 따라 필요한 할인정책을(fixDiscountPolicy, rateDiscountPolicy) 꺼내서 사용하면 된다.
'Programming > Spring' 카테고리의 다른 글
[Spring] 싱글톤과 프로토타입 스코프 (1) | 2024.01.10 |
---|---|
[Spring] 스프링 컨테이너가 빈을 등록하는 방법 (1) | 2024.01.10 |
[Spring] 순환참조 문제 해결 (1) | 2023.10.04 |
[Spring Boot] Spring Boot + ES 예제 (0) | 2023.09.28 |
[Spring Boot] Spring Boot + Kafka 예제 (0) | 2023.09.26 |