스프링 빈 타입이 중복 될 때 해결책

gwjeon·2021년 10월 2일
1


학습참조
인프런-스프링 핵심 원리 기본편(김영한님)


✅ 중복되는 예시

스프링의 빈 이름이 중복 되는 경우는 다음과 같다.

✔ OrderServiceImpl

@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(DiscountPolicy discountPolicy) { //(1)
        this.discountPolicy = discountPolicy;
    }
}

✔ FixDiscountPolicy

@Component
public class FixDiscountPolicy implements DiscountPolicy{ } //(2)

✔ RateDiscountPolicy

@Component
public class RateDiscountPolicy implements DiscountPolicy{ } //(2)

위 소스코드의 상황은 다음과 같다.

  • 주문서비스를 구현하며 상황에 따라 적용되는 주문 정책이 달리 적용된다.
  • OrderService 인터페이스를 구현하는 OrderServiceImpl 구현체
  • DiscountPolicy 인터페이스를 구현하는 매번 Fix된 고정값을 할인해주는 FixDiscountPolicy 구현체
  • DiscountPolicy 인터페이스를 구현하는 상황에 따라 정해진 비율로 할인해주는 RateDiscountPolicy 구현체

✔ 문제점 발생

  • (1) : @Autowired 자동 의존성 주입시 빈 조회 대상이 타입을 기준으로 조회를 하기 때문에 위 예제에서는 DiscountPolicy 타입을 대상으로 빈을 조회함.
  • (2) : 하지만 실제로 DiscountPolicy 타입을 구현하고 있는 빈은 FixDiscountPolicy, RateDiscountPolicy 2개가 되어 '중복된 타입의 빈이 2개가 있다'며 에러가 발생한다.

✅ 문제점 해결을 위한 3가지 방안

문제점 해결을 위한 3가지 방안은 다음과 같다.

  • 필드명 또는 Parameter명 변경으로 문제점 해결
  • @Qualifier Annotation
  • @Primary Annotation

✔ 필드명 또는 Parameter명 변경으로 문제점 해결

@Autowired 자동 주입시 다음과 같은 순서로 이루어 진다.

    1. 동일한 데이터 타입을 가지는 빈을 찾아낸다.
    1. 빈이 2개 이상일 경우 필드명 또는 Parameter 이름으로 빈 이름을 찾아낸다.
@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(DiscountPolicy rateDiscountPolicy) { //(1)
        this.discountPolicy = discountPolicy;
    }
}
  • (1) : DiscountPolicy 타입을 가지는 빈이 fixDiscountPolicy, rateDiscountPolicy 2개이므로 Parameter 이름으로 빈을 찾는다. 즉 스프링 컨테이너에 rateDiscountPolicy라는 빈이 있으므로 rateDiscountPolicy 빈이 주입된다.

✔ @Qualifier Annotation

@Qualifier은 다음과 같은 순서로 이루어 진다.

    1. 주입시 @Qualifier가 있다면 @Qualifier로 생성된 같은 빈 별칭을 찾는다.
    1. 만약 별칭으로 찾지 못할 경우 별칭이름으로 빈을 추가로 찾는다.

✔ RateDiscountPolicy

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

✔ OrderServiceImpl

@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(@Qualifier("mainDiscountPolicy") DiscountPolicy rateDiscountPolicy) { //(2)
        this.discountPolicy = discountPolicy;
    }
}
  • (1) : rateDiscountPolicy 빈의 선언부에 @Qualifier Annoatation을 선언하고 옵션값으로 mainDiscountPolicy를 지정. 빈의 별칭을 지정한 것.
  • (2) : 의존 관계 주입시 @Qualifier mainDiscountPolicy가 등록된 빈을 찾는다. 만약 없을 경우 mainDiscountPolicy 이름을 가지는 빈을 찾아 주입한다.
  • 하지만.. 프로그램이 점점 복잡하고 분석이 어려워지는 코드는 좋은 설계가 아니다. 따라서 @Qualifier를 사용할 것이라면 정확하게 @Qualifier를 사용하여 찾는 용도로만 사용하는게 좋다.
  • @Autowired가 아닌 @Bean의 수동 등록 방법으로도 동일하게 사용 할 수 있다.

✔ @Primary Annotation

✔ RateDiscountPolicy

@Component
@Primary (2)
public class RateDiscountPolicy implements DiscountPolicy{ }

✔ OrderServiceImpl

@Component
public class OrderServiceImpl implements OrderService{

    private final DiscountPolicy discountPolicy;

    @Autowired //의존관계 자동 주입
    public OrderServiceImpl(DiscountPolicy rateDiscountPolicy) { (1)
        this.discountPolicy = discountPolicy;
    }
}
  • (1) : 타입이 중복되는 빈이 있을 경우
  • (2) : @Primary Annotation이 붙은 빈이 최우선이 되므로 중복되는 빈은 무시한다. 즉 fixDiscountPolicy 빈은 무시되고 rateDiscountPolicy빈이 주입된다.

✅ @Primary와 @Qualifier 우선순위

@Primary 는 기본값 처럼 동작하는 것이고, @Qualifier 는 매우 상세하게 동작한다. 이런 경우 어떤 것이 우선권을 가져갈까? 스프링은 자동보다는 수동이, 넒은 범위의 선택권 보다는 좁은 범위의 선택권이 우선순위가 높다. 따라서 여기서도 @Qualifier 가 우선권이 높다.

✔ 활용

코드에서 자주 사용하는 메인 데이터베이스의 커넥션을 획득하는 스프링 빈이 있고, 코드에서 특별한 기능으로 가끔 사용하는 서브 데이터베이스의 커넥션을 획득하는 스프링 빈이 있다고 생각해보자. 메인 데이터베이스의 커넥션을 획득하는 스프링 빈은 @Primary 를 적용해서 조회하는 곳에서 @Qualifier 지정 없이 편리하게 조회하고, 서브 데이터베이스 커넥션 빈을 획득할 때는 @Qualifier 를 지정해서 명시적으로 획득 하는 방식으로 사용하면 코드를 깔끔하게 유지할 수 있다.


profile
ansuzh

0개의 댓글