반응형

스프링 컨테이너는 조회 대상의 타입이 2개 이상일 경우 에러를 발생 시킨다.
이 경우 하위 타입으로 지정해도 되지만 DIP, OCP 위반이다.

@Autowired 필드 명 or 파라미터 명으로 빈 이름 매칭


아래와 같은 코드가 있을 경우 같은 타입의 빈이 두개이므로 등록을 할 수 없는 에러가 발생한다.
DiscountPolicy타입으로 fixDiscountPolicy,rateDiscountPolicy 2개의 하위 타입이 있기 때문에 나는 에러이다.(NoUniqueBeanDefinitionException)
의존 주입을 시도할 때 발생하는 것이다.

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

        @Autowired
    public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
}

public class FixDiscountPolicy implements DiscountPolicy {...}
public class RateDiscountPolicy implements DiscountPolicy {...}

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:\
No qualifying bean of type 'hello.core.discount.DiscountPolicy' available: expected single matching bean but found 2: fixDiscountPolicy,rateDiscountPolicy

생명 주기

@Autowired 파라미터나 필드명을 fixDiscountPolicy와 같이 변경하면 된다.

  1. 타입 매칭 시도 후 중복이 된다면?
  2. 이름 매칭을 시도하게 된다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy fixDiscountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = fixDiscountPolicy;
}
  • 필드, 수정자 주입에도 동일하다.

@Qualifier


주입 시 추가로 구분자만 할당 해주는 방법이다. 빈 이름을 변경하는 것은 아니다.
그냥 별명일 뿐이다.

아래와 같이 빈에 별명을 붙여준다.

@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {}

@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {}

주입 시 원하는 @Qualifier를 선택한다.
그럼 RateDiscountPolicy가 등록되게 된다.

@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
      this.memberRepository = memberRepository;
      this.discountPolicy = discountPolicy;
}
  • 필드, 수정자 주입에도 동일하다.

생명 주기

  1. @Qualifier끼리 매칭
  2. 빈 이름으로 매칭
  3. NoSuchBeanDefinitionException

@Primary


우선 순위를 정하는 방법이다.

rateDiscounPolicy가 우선권을 가지도록 하는 코드이다.

@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;
}

@Qualifier와 @Primary중 뭘 사용하지?


@Qualifier를 주입 받을 때 모든 코드에 @Qualifier 달아줘야 하므로 불편하고, 코드가 지저분해 질 수 있다.
하지만 @Primary는 그럴 필요가 없다.

이렇게 결정하자.

90% 비율로 메인 DB가 사용되고, 10%비율로 서브 DB가 사용된다고 하자.
이때 메인에는 @Primary를 사용하고, 가끔 사용되는 서브 DB에는 명시적으로 @Qualifier를 지정해서
코드를 깔끔하게 유지할 수 있다. 즉, 필요할 경우만 명시적으로 @Qualifier를 사용하는 것이다.

우선 순위

스프링은 자동 보다는 수동이, 넓은 범위 보다는 좁은 범위의 선택이 우선 시 된다.
즉, @Primary 보다 @Qualifier가 더 상세하기 때문에 이와 같이 사용할 수 있다.

@Qualifier 애노테이션의로 만들기


@Qualifier를 사용하게 되면 문자열이기 때문에 런타임 에러를 잠재하게 된다.
그래서 컴파일 에러로 잡기 위해 애노테이션을 활용할 수 있다.

@Target({ElementType.PARAMETER, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("mainDiscountPolicy")
public @interface MainDiscountPolicy {
}

@MainDiscountPolicy
@Component
public class RateDiscountPolicy implements DiscountPolicy {...}

@Component
public class OrderServiceImpl implements OrderService {

    private final MemberRepository memberRepository;
    private final DiscountPolicy discountPolicy;

    public OrderServiceImpl(MemberRepository memberRepository, @MainDiscountPolicy DiscountPolicy discountPolicy) {
        this.memberRepository = memberRepository;
        this.discountPolicy = discountPolicy;
    }
        ...
}
반응형
복사했습니다!