
오늘도 김영한 강사님의 강의를 들었다. 이분의 개발 실력이면 현재 연봉을 얼마나 받을 수 있을까 같은 생각을하면서, 선생님이 만드신 로드맵을 한발자국 밟아갔다.
"네카라쿠배를 향한 그날까지" - 이현제
오늘 내용은 모각소-6 마지막 내용에서 이어나가니 잘 살펴보길 바란다.
간단하게 문제를 정리하자면,
DiscountPolicy의 두 구현체 FixDiscountPolicy, RateDiscountPolicy이 @Component로 되어있을때
@Autowired
private DiscountPolicy discountPolicy
처럼 DiscountPolicy의 변수가 의존관계 자동주입이 실행된다면 어떤 현상이 발생할까?
NoUniqueBeanDefinitionException 오류가 발생한다.
그럼 위와 같은 문제를 해결할 수 있는 방법에 대해서 설명해보도록 하겠다.
조회 대상 빈이 2개 이상일때 해결 방법
1. @Autowired 필드 명 매칭
2. @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
3. @Primary 사용
@Autowired는 타입 매칭을 시도학, 이때 여러 빈이 있으면 필드이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
기존코드
@Autowired
private DiscountPolicy discountPolicy
필드 명을 빈 이름으로 변경
@Autowired
private DiscountPolicy rateDiscountPolicy
필드 명이 rateDiscountPolicy 이므로 정상 주입된다.
즉, @Autowired 매칭 우선순위는
1. 타입 매칭 : DiscountPolicy와 같은 타입의 빈을 찾는다.
2. 필드명, 파라미터 명으로 매칭 : 타입 매칭의 결과가 2개 이상일때 필드명, 파라미터 명으로 빈 이름을 매칭한다.
하지만 필드 명으로 의존관계를 주입하게 된다면 DiscountPolicy를 변경할때마다 빈이름으로 변경해줘야 하기때문에 유지보수 측면에서 좋지않다.
@Qualifier는 추가 구분자를 붙여주는 방법이다. 주입시 추가적인 방법을 제공하는 것이지 빈 이름을 변경하는 것은 아니다.
빈 등록시 @Qualifier를 붙여 준다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
이후 주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
생성자 자동 주입 예시
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
그럼 @Qualifier로 주입할 때 @Qulifier("mainDiscountPolicy")를 못찾으면 어떻게 될까?
-> 그러면 mainDiscountPolicy라는 이름의 스프링 빈을 추가로 찾는다. 하지만 경험상 @Qualifier는 @Qualifier를 찾는 용도로만 사용한느게 명확하고 좋다.
그리고 다음과 같이 직접 빈 등록시에도 @Qualifier를 동일하게 사용할 수 있다.
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
**@Qualifier 정리
1. @Qualifier끼리 매칭
2. 빈 이름 매칭
3. NoSuchBeanDefinitionException 예외 발생
@Primary는 우선순위를 정하는 방법이다. 그냥 사실 이방법을 사용하면 된다.
rateDiscountPolicy가 우선권을 가지도록 하자.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
사용 코드
//생성자
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//수정자
@Autowired
public DiscountPolicy setDiscountPolicy(DiscountPolicy discountPolicy) {
this.discountPolicy = discountPolicy;
}
사용코드를 바꿀필요도 없이 discountPolicy에는 @Primary가 사용된 rateDiscountPolicy의 인스턴스가 할당된다.
해당 타입의 스프링 빈이 다 필요한 경우도 있다.
예를 들어서 할인 서비스를 rate,fix 둘 중 하나로 선택이 가능하게 만들고 싶을때는 스프링을 사용하여, 전략 패턴을 매우 간단하게 구현할 수 있다.
코드를 보면서 설명하겠다.
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);
int rateDiscountPrice = discountService.discount(member,20000,"rateDiscountPolicy");
assertThat(rateDiscountPrice).isEqualTo(2000);
}
static class DiscountService {
private final Map<String, DiscountPolicy> policyMap;
private final List<DiscountPolicy> policies;
@Autowired
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);
}
}
}
로직 분석
주입 분석
결국 discountServices는 fixDiscountPolicy, rateDiscountPolicy 두개의 인스턴스를 할당 받는다고 생각하면 된다.
편리한 자동 기능을 기본으로 사용하다
어떤 경우에 컴포넌트 스캔과 자동 주입을 사용하고, 어떤 경우에 설정 정보를 통해서 수동으로 빈을 등록하고, 의존관계도 수동으로 주입해야 할까?
결론부터 말하면, 스프링이 나오고 시간이 갈 수록 점점 자동을 선호하는 추세다. 스프링은 @Component 뿐만 아니라 @Controller, @Service, @Repository처럼 계층에 맞추어 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원한다. 거기에 더해서 최근 스프링 부트는 컴포넌트 스캔을 기본으로 사용하고, 스프링 부트의 다양한 스프링 빈들도 조건이 맞으면 자동으로 등록하도록 설계한다.
그리고 프로젝트가 커지면 커질 수록 @Bean을 사용해서 몇 백개가 될 수 있는 빈등록을 일일이 해주는것은 번거로우므로 자동으로 할 수 있도록 하자.
Tip: 자동 빈 등록으로 할 component들은 한 패키지로 모아두자.
저번 모각소에 이어서 같은 강의내용을 다루는데 모각소 모임이 있는 때만 공부를 하면 내용을 계속해서 까먹는 것 같다.까먹지 않기위해 모각소를 제외 하고서라도 계속해서 개발에 시간을 투자해야한다는 것을 다시한번 깨달았다. 다음주부터는 복습까지 할예정이다. 벌써 2월이고 모각소 모임도 끝나가는데, 개발 지식을 뿐만 아니라 공부하는 습관을 배워가는 것 같아서 좋다. 개발이 습관이 될때까지 열심히하기.
출처 - 김영한 강사님의 스프링 기본편
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-%ED%95%B5%EC%8B%AC-%EC%9B%90%EB%A6%AC-%EA%B8%B0%EB%B3%B8%ED%8E%B8