@Autowired
는 타입(Type)으로 조회한다.@Autowired
private DiscountPolicy discountPolicy
ac.getBean(DiscountPolicy.class)
DiscountPolicy
하위 타입인 FixDiscountPolicy
, RateDiscountPolicy
둘 다 스프링 빈으로 선언하면 어떻게 될까?DiscountPolicy
하위 타입 모두 스프링 빈으로 선언해본다.@Component
public class RateDiscountPolicy implements DiscountPolicy{
@Component
public class FixDiscountPolicy implements DiscountPolicy {
아래와 같이 NoUniqueBeanDefinitionException
오류가 발생한다.
이때 하위 타입으로 지정할 수 도 있지만, 하위 타입으로 지정하는 것은 DIP를 위반하고 유연성이 떨어진다.
이름만 다르고, 완전히 똑같은 타입의 스프링 빈이 2개 있을 때 해결이 안된다.
스프링 빈을 수동 등록해서 문제를 해결해도 되지만, 의존 관계 자동 주입에서 해결할 수 있는 방법이 여러개 있다.
@Autowired
필드명 배치@Qualifer
→@Qualifer
끼리 매칭 → 빈 이름 매칭@Primary
사용
@Autowired
는 타입 매칭을 시도하고, 이때 여러 빈이 있으면 필드 이름, 파라미터 이름으로 빈 이름을 추가 매칭한다.@Autowired
public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Autowired
public OrderServiceImp(MemberRepository memberRepository, DiscountPolicy rateDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = rateDiscountPolicy;
}
rateDiscountPolicy
이므로 정상 주입된다.Qualifier
는 추가 구분자를 붙여주는 방법@Component
@Qualifier("mainDicountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}
@Component
@Qualifier("fixDicountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}
@Autowired
public OrderServiceImpl(MemberService memberService,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy){
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qaulifier
로 주입할 때 @Qualifier("mainDiscountPolicy")
를 못 찾으면 어떻게 될까?@Qualifier
는 @Qualifier
를 찾는 용도로만 사용하는 것이 좋다.@Qualifier
를 동일하게 사용할 수 있다.@Qualifier
끼리 매칭NoSuchBeanDefinitionException
예외 발생@Autowired
시에 여러 빈이 매칭되면 @Primary
가 우선권을 가진다.rateDiscountPolicy
가 우선권을 가지도록 해보자
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy{}
@Component
public class FixDiscountPolicy implements DiscountPolicy {}
@Primary
와 @Qualifier
중에 어떤 것을 사용하면 좋을까?@Qualifier
의 단점은 주입 받을 때 다음과 같이 모든 코드에 @Qualifier
를 붙여주어야 한다는 점이 있다.@Primary, @Qaulifier 활용
자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은
@Primary
를 적용, 서브 데이터베이스의 커넥션 빈 획득은@Qaulifier
를 지정해서 명시적으로 획득하는 방식 사용하는 방식으로 활용할 수 있다.
우선순위
@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다.
- 스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다.
- 따라서 여기서도 @Qualifier 가 우선권이 높다.
-@Qualifier("mainDiscountPolicy")
이렇게 문자로 적으면 컴파일시 타입 체크가 안된다.
@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
public OrderServiceImp(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
@Qualifier
뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용할 수 있다.해당 타입의 스프링 빈이 다 필요한 경우도 있다.
클라이언트가 할인의 종류(rate, fix)를 선택할 수 있다고 가정하면 가능!
public class AllBeanTest {
@Test
void findAllBean(){
ApplicationContext ac = new AnnotationConfigApplicationContext(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;
@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);
}
}
}
DiscountService는 Map으로 모든 DiscountPolicy
를 주입받는다.
이때 fixDiscountPolicy
, rateDiscountPolicy
가 주입된다.
discount()
메서드는 discountCode를 인자로 받는다.
fixDiscountPolicy
가 넘어오면 map에서 fixDiscountPolicy
스프링 빈을 찾아서 실행한다.Map<String, DiscountPolicy>
: map의 키에 스프링 빈의 이름을 넣어주고, 그 값을 DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.List<DiscountPolicy>
: DiscountPolicy 타입으로 조회한 모든 스프링 빈을 담아준다.참고 - 스프링 컨테이너를 생성하면서 스프링 빈 등록하기
- 스프링 컨테이너는 생성자 클래스 정보를 받는다.
- 클래스 정보를 넘기면 해당 클래스가 스프링 빈으로 자동 등록된다.
new AnnotationConfigApplicationContext(AutoAppConfig.class, DiscountService.class);
2가지 해석
new AnnotationConfigApplicationContext()
를 통해 스프링 컨테이너 생성AutoAppConfig.class
,DiscountService.class
를 파라미터로 넘기면서 해당 클래스를 자동으로 스프링 빈으로 등록한다.스프링 컨테이너를 생성하면서, 해당 컨테이너에 동시에
AutoAppConfig
,DiscountService
를 스프링 빈으로 자동 등록한다.
편리한 자동 기능을 기본으로 사용하자!
어떤 경우에 컴포넌트 스캔과 자동 주입을 사용하고, 어떤 경우에 설정 정보를 통해서 수동으로 빈을 등록하고, 의존관계도 수동으로 주입해야 할까?
@Component
, @Controller
, @Service
, @Repository
계층에 맞추어 일반적인 애플리케이션 로직을 자동으로 스캔할 수 있도록 지원한다.애플리케이션은 업무 로직과 기술 지원 로직으로 나눌 수 있다.
업무 로직 빈
- 웹을 지원하는 컨트롤러, 핵심 비즈니스 로직이 있는 서비스, 데이터 계층의 로직을 처리하는 리포지토리 등이 모두 업무 로직!
- 비즈니스 요구사항을 개발할 때 추가되거나 변경된다.
- 숫자도 매우 많고, 어느정도 유사한 패턴이 있기 때문에 자동 기능 사용 추천!
- 문제가 발생해도 어떤 곳에서 문제가 발생했는지 명확하게 파악하기 쉽다.
기술 지원 빈
- 기술적인 문제나 공통 관심사 (AOP)를 처리할 때 주로 사용된다.
- 데이터 베이스 연결이나, 공통 로그 처리 처럼 업무 로직을 지원하기 위한 하부 기술이나 공통 기술
- 업무로직과 비교해서 그 수가 매우 적고, 보통 애플리케이션 전반에 걸쳐 광범위하게 영향을 미친다.
- 적용이 잘 되고 있는지 아닌지 조차 파악하기 어려운 경우가 많다.
- 수동 빈 등록 사용을 추천!
⭐수동 빈 등록은 기술 지원 빈을 사용해 명확하게 드러내는 것이 좋다!
애플리케이션에 광범위하게 영향을 미치는 기술 지원 객체는 수동 지원 빈으로 등록해서 설정 정보에 바로 나타나게 하는 것이 유지보수에 좋다.
List, Map
을 생각해보자.DiscountService
가 의존관계를 자동 주입으로 Map<String, DiscountPolicy>
에 주입을 받는 상황을 생각해보자!@Configuration
public class DiscountPolicyConfig {
@Bean
public DiscountPolicy rateDiscountPolicy(){
return new RateDiscountPolicy();
}
@Bean
public DiscountPolicy fixDiscountPolicy(){
return new FixDiscountPolicy();
}
}
DiscountPolicy
구현 빈들만 따로 모아 특정 패키지에 모아두자!참고
- 스프링과 스프링 부트가 자동으로 등록하는 수많은 빈들은 예외!
- 스프링 부트의 경우
DataSource
같은 데이터베이스 연결에 사용하는 기술 지원 로직까지 내부에서 자동으로 등록하는데, 이런 부분은 잘 참고해서 사용하는 것이 좋다.- 내가 직접 기술 지원 객체를 스프링 빈으로 등록한다면 명확하게 드러내는 것이 좋다.