목차
- 의존관계 자동 주입 - 다양한 의존관계 주입 방법
- 의존관계 자동 주입 - 옵션 처리
- 의존관계 자동 주입 - 생성자 주입을 선택해라!
- 의존관계 자동 주입 - 롬복과 최신 트랜드 (생성자 주입을 편리하게 사용하기)
- 의존관계자동주입 - 조회빈이2개이상-문제
- 의존관계 자동 주입 - @Autowired 필드 명, @Qualifier, @Primary
- 의존관계 자동 주입 - 애노테이션 직접 만들기
- 의존관계 자동 주입 - 조회한 빈이 모두 필요할 때, List, Map
- 의존관계 자동 주입 - 자동, 수동의 올바른 실무 운영 기준
조회 빈이 2개 이상인 경우
조회 빈이 2개 이상인 경우 오류가 발생한다.
일단 해당 상황을 재현해보자.
문제
현재 OrderServiceImpl는
MemberRepository(인터페이스), DiscountPolicy(인터페이스)를 의존하고 있으며,
이는 구현체가 아닌 인터페이스(추상화)에만 의존한 올바른 설계이다.
@Component
public class OrderServiceImpl implements OrderService{
//2. 스프링 컨테이너 사용 (문제점해결 / 의존관계 주입 - 생성자 주입방법)
private final MemberRepository memberRepository;// -> 오직 인터페이스만 존재 (추상화에만 의존함 DIP)
private final DisountPolicy disountPolicy; //인터페이스 (추상)에만 의존함 (DIP)
@Autowired// 자동으로 의존관계(MemberRepo , discountpolicy)를 주입해줌
public OrderServiceImpl(MemberRepository memberRepository, DisountPolicy disountPolicy){
this.disountPolicy =disountPolicy;
this.memberRepository=memberRepository;
}
DiscountPolicy로만 생각해보자,
현재 DiscountPolicy는 구현체가 2개이다 (fixed DP, RateDP)
그런데, @Component annotation을 RateDP에만 해두었기 때문에, 스프링 빈으로 등록되는 것은 RateDP뿐이다.
따라서, DiscountPolicy를 의존하고 있는 OrderServiceImpl이 의존관계를 주입받을 때,
@Autowired는 타입(Type)으로 조회하는데 Type DiscountPolicy로 조회했을 때 선택되는 빈이 RateDiscountPolicy 1개이므로 아무런 문제가 발생하지 않는다.
그런데, 이때 FixedDiscountPolicy도 @annotation을 추가하여 스프링 빈으로 등록하게 되면
이렇게되어서 에러가난다.
추가해서 돌려보면 다음과 같이 "expected single matching bean but found 2"라는 에러가 발생한다.
하지만 그렇다고, 의존 관계를 DiscountPolicy가 아닌 구현체 중 하나로 한다면 에러는 없어지겠지만,
DIP를 위반하고 유연성이 떨어진다.
스프링 빈을 수동으로 등록하는 방법으로 해결 가능하지만, 수동이 아닌 자동관계 주입에서도 해결 방안이 있다.
해결방안
- @Autowired 필드 명 매칭
- @Qualifier → @Qualifier끼리 매칭 → 빈 이름 매칭
- @Primary 사용
@Autowired 필드 명 매칭
@Autowired의 특성으로, 타입 매칭을 시도했을 때, 여러 빈이 있으면,
필드이름, 파라미터 이름으로 빈 이름을 매칭하여 본다.
즉, 생성자의 파라미터 이름을 빈 이름으로 추가 매칭하면 이를 해결할 수 있다.
OrderServiceImpl
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;// -> 오직 인터페이스만 존재 (추상화에만 의존함 DIP)
private final DisountPolicy disountPolicy; //인터페이스 (추상)에만 의존함 (DIP)
@Autowired// 자동으로 의존관계(MemberRepo , discountpolicy)를 주입해줌
public OrderServiceImpl(MemberRepository memberRepository, DisountPolicy rateDiscountPolicy){
this.disountPolicy = rateDiscountPolicy;
this.memberRepository = memberRepository;
}
다음과 같이 매개변수의 discountPolicy를 rateDiscountPolicy로 변경하면, 빈 중 rateDiscountPolicy로 이를 매칭한다.
@Autowired는
- 타입매칭을 진행 (1개 면 종료)
- 그 후 결과가 2개 이상이면, 필드명, 파라미터 명이름으로 빈 이름을 매칭
@Quilifier
추가 구분자를 붙여주는 방법, (빈 이름을 변경하는 것이 아님)
구현체 클래스에 @Qualifier("특별한 구분자") annotation을 사용한다.
그 후, 의존관계 주입시 이를 명시해준다.
RateDiscountPolicy
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DisountPolicy{
@Component
public class OrderServiceImpl implements OrderService{
private final MemberRepository memberRepository;// -> 오직 인터페이스만 존재 (추상화에만 의존함 DIP)
private final DisountPolicy disountPolicy; //인터페이스 (추상)에만 의존함 (DIP)
@Autowired// 자동으로 의존관계(MemberRepo , discountpolicy)를 주입해줌
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DisountPolicy discountPolicy){
this.disountPolicy = discountPolicy;
this.memberRepository=memberRepository;
}
이렇게 해주면 @Qualifier로 먼저 특별한 구분자로 이를 찾아낸 후 주입해준다.
만약, 특별한 구분자(mainDiscountPolicy를 찾지못한 경우)로 찾아내지 못하는 경우, 이 이름의 스프링 빈을 추가로 찾는다.
(but, @Qualifier는 @Qualifier를 찾는 용도로만 사용하는게 정확하고 좋다.)
여러가지 사용 방법
1. 생성자 자동 주입
@Autowired
public OrderServiceImpl(MemberRepository memberRepository,
@Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
2. 수정자 자동 주입
@Autowired
public DiscountPolicy setDiscountPolicy(@Qualifier("mainDiscountPolicy")
DiscountPolicy discountPolicy) {
return discountPolicy;
}
3. 직접 빈 등록 (@Bean)
@Bean
@Qualifier("mainDiscountPolicy")
public DiscountPolicy discountPolicy() {
return new ...
}
@Qualifier는
- @Qualifier끼리 매칭
- 빈 이름 매칭
- NoSuchBeanDefinitionException 예외 발생
@Primary
가장 많이 사용되는 방법
우선순위를 정하는 방법으로, @Autowired시에 여러 빈이 매칭되면 @Primary가 우선권을 가진다.
여러 빈 중에 주로 사용되는 구현체에 @Primary를 부착해준다.
@Component
@Primary
public class RateDiscountPolicy implements DisountPolicy{
이렇게 되면, 의존관계 주입부분에서는 아무런 변화 없이 그대로 사용해도 된다.
@Component
public class OrderServiceImpl implements OrderService{
@Autowired// 자동으로 의존관계(MemberRepo , discountpolicy)를 주입해줌
public OrderServiceImpl(MemberRepository memberRepository, DisountPolicy discountPolicy){
this.disountPolicy = discountPolicy;
this.memberRepository=memberRepository;
}
이 부분을 변경할 필요가 없으므로 간단하고 잘 사용된다.
Qualifier vs Primary
Qualifier 의 단점은 사용하는 모든 부분에 @Qualifier 를 추가해주어야 한다는 사실이다.
우선순위
Qualifier > Primary : Qualifier 의 우선순위가 높다.
Primary는 기본값 처럼 작동하고, Qualifier 는 매우 상세하게 작동한다.
범위가 Qualifier 가 더 좁기 때문에 우선순위가 더 높다.
사용 예
데이터베이스가 메인용도 (코드에서 자주 사용하는 90%의 작업량 차지), 보조(코드에서 특별한 기능으로 가끔 사용하는 10%의 작업량차지)용도가 있다.
두가지 데이터베이스를 획득하여 사용하는 스프링 빈이 존재할 때,
이런 경우 메인용도의 경우 Primary를 사용해서 지정없이 편리하게 사용하고,
서브는 가끔만 사용하기 때문에 Qualifier를 이용해서 특수한 상황으로 지정해주면 코드를 깔끔하게 유지 가능하다.
애노테이션 직접 만들기
(주로 Primary로 해결되는 상황이면 그렇게 하지만 다양한 상황을 정의해서 사용할 때 유용 / cmd + B로 추적도 가능 )
@Qualifier("mainDiscountPolicy") //이전 게시글 참고
를 대체할 수 있는 애노테이션을 직접 제작해 보도록 한다.
직접 애노테이션을 만들기 위하여, 패키지와 클래스를 만든다.
MainDiscountPolicy
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)
@Documented
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}
@Target ~ @Documented는 @Qualifier 에서 가져온 것
이제 @Qulifier("mainDiscountPolicy")로 작성하던 것을 @MainDiscountPolicy로 대체가능하다.
예를 들면, (이전 게시글) @Qulifier예제를 이렇게 변경할 수 있다.
RateDiscountPolicy
@Component
@MainDiscountPolicy
public class RateDiscountPolicy implements DisountPolicy{
OrderServiceImpl
@Component
public class OrderServiceImpl implements OrderService{
//2. 스프링 컨테이너 사용 (문제점해결 / 의존관계 주입 - 생성자 주입방법)
private final MemberRepository memberRepository;// -> 오직 인터페이스만 존재 (추상화에만 의존함 DIP)
private final DisountPolicy disountPolicy; //인터페이스 (추상)에만 의존함 (DIP)
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DisountPolicy discountPolicy){
this.disountPolicy = discountPolicy;
this.memberRepository=memberRepository;
}
<정리>
애노테이션에는 상속이라는 개념X
이렇게 여러 애노테이션을 모아서 사용하는 기능은 스프링이 지원해주는 기능이다.
@Qulifier 뿐만 아니라 다른 애노테이션들도 함께 조합해서 사용할 수 있다.
단적으로 @Autowired도 재정의 할 수 있다. 물론 스프링이 제공하는 기능을 뚜렷한 목적 없이 무분별하게 재정의 하는 것은 유지보수에 더 혼란만 가중할 수 있다.
'Backend > Spring' 카테고리의 다른 글
[Spring] 빈 생명주기 콜백 (0) | 2021.10.07 |
---|---|
[Spring] 의존관계 자동 주입 3) List & Map (0) | 2021.10.06 |
[Spring] 의존관계 자동 주입 1) 의존 주입 방법 (0) | 2021.10.06 |
[Spring] 컴포넌트 스캔 2) 기타 사항 (0) | 2021.09.30 |
[Spring] 컴포넌트 스캔 1) 컴포넌트 스캔과 의존관계 주입 (0) | 2021.09.30 |