[Spring] 스프링 빈 충돌 이슈 관리
스프링에서 빈들을 관리할 때 객체지향의 다형성이라는 특징을 이용하여 애플리케이션의 유연함을 늘리게 된다. 이 때 개발자는 의존 주입을 하기 위해 하위 타입의 객체를 만들게 되는데 이 때 단순히 타입을 입력하여 자동 주입을 받게 되면 상위 타입에 매칭되는 빈들 중에서 어떤 빈을 사용해야하는지 혼동이 와 충돌이 생길 가능성이 있다. 이를 해결하기 위해서는 스프링에서 어떻게 관리해주어야 하는지 알아보자.
스프링에서 빈 조회가 2개 이상 되는 문제를 해결하기 위한 방법은 다음과 같은데 하나씩 알아보자.
- @Autowired 필드 명 매칭
- @Qualifier -> @Qualifier끼리 매칭 -> 빈 이름 매칭
- @Primary 사용
@Autowired 필드 명 매칭
Autowired로 자동주입 받을 때 스프링은 다음과 같이 작동한다.
1. 주입 받을 객체 타입의 빈이 1개일 경우에는 바로 주입한다.
2. 그런데 타입이 같은 빈이 2개 이상일 경우(지금은 FixDiscountPolicy와 RateDiscountPolicy)에는 충돌이 발생하는데 이 때 필드명을 인식하거나 생성자의 변수(파라미터) 명을 인식하여 작동할 수 있게 된다.
- 스프링은 관련 타입의 모든 하위 자식 빈들을 모두 스캔한다.
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, DiscountPolicy fixDiscountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = fixDiscountPolicy;
@Qualifier 필드 명 매칭
두번째 방식은 `@Qualifier` 어노테이션을 이용하여 빈이 될 객체의 이름에 정보를 붙여 구분할 수 있게 만들어 주는 것이다. 먼저, 다음과 같이 사용될 객체에 Qualifier 어노테이션으로 값을 지정해준다.
@Component
@Qualifier("mainDiscountPolicy")
public class RateDiscountPolicy implements DiscountPolicy {
@Component
@Qualifier("fixDiscountPolicy")
public class FixDiscountPolicy implements DiscountPolicy {
이 후 의존 주입이 필요한 곳에서 사용하고 싶은 value를 Qualifier 어노테이션과 함께 붙여주면 된다.
@Component
//@RequiredArgsConstructor
public class OrderServiceImpl implements OrderService {
private final MemberRepository memberRepository;
private final DiscountPolicy discountPolicy;
@Autowired
public OrderServiceImpl(MemberRepository memberRepository, @Qualifier("mainDiscountPolicy") DiscountPolicy discountPolicy) {
this.memberRepository = memberRepository;
this.discountPolicy = discountPolicy;
}
`@Qualifier`를 사용한 방식은 생성자, 수정자, 필드 자동 주입 모두 가능하다. 그리고 또한 만약 우리가 Qualifier의 value로 사용한 mainDiscountPolicy를 빈에서 찾지 못하게 되면 해당 이름으로 된 스프링 빈을 다시 찾기 시작한다. 하지만, Qualifier를 사용하여 빈을 찾는 방식보다는 지금처럼 Qualifier를 찾는 용도로 사용하는게 명확하고 바람직하다.
또한, 빈을 컴포넌트 스캔 방식이 아니라 직접 등록하는 방식에서도 똑같이 사용할 수 있다.
`@Qualifier`가 작동하는 방식을 정리를 해보자면 다음과 같다.
- @Qualifier가 붙어있는 객체끼리 먼저 매칭을 한다.
- 없다면 해당 value값으로 된 빈 이름이 존재하는지 확인한다.
- 그래도 없다면 `NoSuchBeanDefinitionException`을 발생시킨다.
@Primary 필드 명 매칭
포스트의 문제를 해결하기 위한 방법 중 가장 자주 사용되고 간단한 방법이다. 가장 간단하고 쉬운만큼 한계점 또한 존재한다.
`@Primary`는 우선순위를 정하여 여러 빈이 탐색되면 우선적으로 사용되게 하는 어노테이션이다.
@Component
@Primary
public class RateDiscountPolicy implements DiscountPolicy {
어떤 방식이 좋은가?
이렇게 되면 Qualifier와 Primary 중 어떤 것이 유용하고 좋은지 헷갈릴 수 있다.
먼저, Qualifier의 단점은 주입 받을 때 모든 코드에 Qualifier 어노테이션을 붙여주아야 한다는 단점을 가지고 있다.
각 상황에 맞는 방식이 무엇인지 알아보자면 DB A와 B 두 개로 서비스를 하고 있는 상황이 있다. 주로 사용되는 DB는 A이며 80 퍼센트는 대부분 이 DB를 사용한다고 생각해보자. 이 때 대부분의 A DB를 사용하는 서비스 코드는 `@Primary`를 달아주고 20 퍼센트의 점유율을 가지는 B에 대해서는 `@Qualifier`를 통해 관리를 해주게 되면 깔끔한 코드가 완성될 것이다. 이때 어노테이션에 대한 우선순위 문제가 생기게 되는데 위 상황에서는 `@Primary`를 기본적으로 사용하겠다는 의미가 된다.
스프링은 자동보다는 수동이, 넓은 범위의 선택권 보다는 좁은 범위의 선택권이 우선 순위가 높다. 따라서 `@Qualifier`가 `@Primary`보다 우선권이 높으므로 위와 같은 어플리케이션 개발이 가능하다.