라이브러리 적용시 설정
- Preferences Annotation Processors 검색 Enable annotation processing 체크
DiscountPolicy의 하위 타입인 FixDiscountPolicy, RateDiscountPolicy 둘다 스프링 빈으로 선언 시
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Component
public class RateDiscountPolicy implements DiscountPolicy {}
의존관계 자동 주입을 실행해보면
@Autowired
private DiscountPolicy discountPolicy
NoUniqueBeanDefinitionException 오류가 발생한다. fixDiscountPolicy, rateDiscountPolicy 두개가 발견되었다는 것이다. 의존 관계 자동 주입에서 해결하는 여러 방법을 알아 보겠다.
// 생성자 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
(기존 코드) DiscountPolicy discountPolicy -->(바뀐 코드) DiscountPolicy rateDiscountPolicy
테스트 결과
@Autowired 매칭 정리
@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;
}
테스트 결과
@Qualifier를 찾는 용도로만 사용하는게 명확하고 좋다.
@Primary 는 우선순위를 정하는 방법이다. @Autowired 시에 여러 빈이 매칭되면 @Primary 가 우선권을 가진다.
rateDiscountPolicy 가 우선권을 가지도록 설정
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{
테스트 결과
우선순위(@Primary>@Qualifier)
@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다. 이런 경우 어떤 것이 우선권을 가져갈까? 스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 높다. 따라서 여기서도 @Qualifier 가 우선권이 높다.
java/com/example/springex1/annotation/MainDiscountPolicy.java
package com.example.springex1.annotation;
import org.springframework.beans.factory.annotation.Qualifier;
import java.lang.annotation.*;
@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{
// 생성자 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@MainDiscountPolicy 등록, 추가
테스트 결과
애노테이션에는 상속이라는 개념이 없다. 이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링이지원해주는 기능이다.
의도적으로 정말 해당 타입의 스프링 빈이 다 필요한 경우가 있다. 클라이언트가 할인의 종류를 선택할 수 있을때 등등
java/com/example/springex1/autowired/AllBeanTest.java
package com.example.springex1.autowired;
import com.example.springex1.AutoAppConfig;
import com.example.springex1.discount.DiscountPolicy;
import com.example.springex1.member.Grade;
import com.example.springex1.member.Member;
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;
import static org.assertj.core.api.Assertions.assertThat;
public class AllBeanTest {
@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);
int discountPrice = discountService.discount(member, 10000, "fixDiscountPolicy");
assertThat(discountService).isInstanceOf(DiscountService.class);
assertThat(discountPrice).isEqualTo(1000);
}
static 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);
System.out.println("discountCode = " + discountCode);
System.out.println("discountPolicy = " + discountPolicy);
return discountPolicy.discount(member, price);
}
}
}
결과
애플리케이션은 크게 업무 로직과 기술 지원 로직으로 나눌 수 있다.
참고
김영한: 스프링 핵심 원리 - 기본편(인프런)
Github - https://github.com/b2b2004/Spring_ex