이 글은 스프링 [핵심원리 - 기본편]을 듣고 정리한 내용입니다
@Autowired
는 타입으로 조회한다.@Autowired
private DiscountPolicy discountPolicy
ac.getBean(DiscountPolicy.class)
와 유사하게 동작한다.DiscountPolicy
의 하위타입인 FixDiscountPolicy
와 RateDiscountPolicy
가 둘다 스프링 빈으로 선언되어있으면 오류가 발생한다. @Component //스프링 빈 등록
public class FixDiscountPolicy implements DiscountPolicy {}
@Component //스프링 빈 등록
public class RateDiscountPolicy implements DiscountPolicy {}
@Autowired
private DiscountPolicy discountPolicy
NoUniqueBeanDefinitionException
오류가 발생한다. NoUniqueBeanDefinitionException: No qualifying bean of type
'hello.core.discount.DiscountPolicy' available: expected single matching bean
but found 2: fixDiscountPolicy,rateDiscountPolicy
@Autowired
필드 명 매칭@Qualifier
-> @Qualifier
끼리 매칭@Primary
사용@Autowired
는 타입 매칭을 시도하고, 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.
기존코드
@Autowired
private DiscountPolicy discountPolicy
@Autowired // 필드명이 rateDiscountPolicy이므로 정상 주입된다.
private DiscountPolicy rateDiscountPolicy
@Qulifier는 추가 구분자를 붙여주는 방법이다.
주입 시 추가적인 방법을 제공하는 것이고, 빈 이름을 바꾸는건 아니다.
빈 등록시
@Component
@Qualifier("mainDiscountPolicy") //Qulifier를 붙여준다.
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
@Autowired // 주입시에 @Qualifier를 붙여주고 등록한 이름을 적어준다.
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy
discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy")
DiscountPolicy discountPolicy) {
return discountPolicy;
}
@Qualifier
로 주입할 때 Qualifier("mainDiscountPolicy")
를 못찾는다면, mainDiscountPolicy
라는 이름의 스프링 빈을 추가로 찾는다.
그러나, @Qualifier
는 @Qualifier
를 찾는 용도로만 사용하자
Qualifier 끼리 매칭
빈 이름 매칭
NoSuchBeanDefinitionException 예외 발생
@Primary
는 우선순위를 정하는 방법이다.@Autowired
할 때, 여러 빈이 매칭되면 @Primary
어노테이션 붙은 곳이 우선권을 가진다@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) {
return discountPolicy;
}
@primary
를 적용해서 조회하는 곳에서 @Qualifier
지정 없이 편하게 조회하고,@Qualifier
를 지정해서 명시적으로 획득하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다. (물론 이때 메인 데이터베이스의 스프링 빈을 등록할 때 @Qualifier
를 지정해주는 것은 상관없다.)@Primiary
와 @Qualifier
중에 @Qualifier
의 우선수위가 더 높다.@Qualifier("mainDiscountPolicy")
이렇게 문자를 만들면 String
이므로, 컴파일 시 타입 체크가 안된다.package hello.core.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)
@Inherited
@Documented
@Qualifier("mainDiscountPolicy") //이부분도 추가해야함.
public @interface MainDiscountPolicy {
}
@Component
@MainDiscountPolicy //만든 어노테이션 추가
public class RateDiscountPolicy implements DiscountPolicy {}
/생성자 자동 주입
@Autowired //파라미터에 Qualifier대신 만든 어노테이션 사용
public OrderServiceImpl(MemberRepository memberRepository,
@MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
//수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@MainDiscountPolicy DiscountPolicy discountPolicy) {
return discountPolicy;
}
package hello.core.autowired;
import static org.assertj.core.api.Assertions.*;
public class AllBeanTest {
@Test
void findAllBean(){
//기존 AutoAppConfig에서도 주입받고, DiscountService(코드 아래 생성: Map이 있는 코드)에서도 주입받음.
ApplicationContext ac = new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
DiscountService discountService = ac.getBean(DiscountService.class);
Member member = new Member(1L, "userA", Grade.VIP);
//fixDiscountPolidcy 적용했을 때 할인 금액
int discountPrice = discountService.discount(member,10000,"fixDiscountPolicy");
//당연한 검증 - 인스턴스 맞는지 확인
assertThat(discountService).isInstanceOf(DiscountService.class);
//fixDiscountPolicy 적용했을때, 할인액이 1000원 맞는지 검증
assertThat(discountPrice).isEqualTo(1000);
//rateDiscountPolicy 적용했을때, 할인액이 2000원 맞는지 검증
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 //disCountPolidct 정책들이 전부 주입됨.
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<String, DiscountPolicy>
: map의 key에 스프링 빈의 이름을 넣어주고, value는 DiscountPolicy
타입으로 조회한 모든 스프링 빈을 담는다.List<DiscountPolicy>
: DiscountPolicy
타입으로 조회한 모든 스프링 빈을 담는다.DiscountService
는 Map으로 모든 DiscountPolicy
를 주입받는다. 위 코드에서는 fixDiscountPolicy
, rateDiscountPolicy
가 주입되었다.discount()
메서드는 discountCode
를 통해 선택한 정책이 넘어오면, 해당 정책에 해당하는 스프링 빈을 찾아서 실행한다.*참고 - 스프링 컨테이너를 생성하면서 스프링 빈 등록하기
new AnnotationConfigApplicationContesxt(AutoAppConfig.class, DiscountService.class);
부분에서
new AnnotationConfigApplicationContext()
: 스프링 컨테이너 생성AutoAppConfig.class
,DiscountService.class
를 파라미터로 넘기면서 해당 클래스들을 자동으로 스프링 빈으로 등록- 즉, 스프링 컨테이너를 생성하면서 동시에 , 해당 컨테이너에 클래스들을 스프링 빈으로 자동 등록 한것이다.
*참고
- 스프링과 스프링 부트가 자동으로 등록하는 수많은 빈들은 예외이다.
- 스프링 부트가 아니라 내가 직접 기술 지원 객체를 스프링 빈으로 등록한다면 수동으로 등록해서 명확하게 들어내는 것이 좋다.