[Spring] Bean이 2개 이상일 때 특정 Bean 선택 방법 (다형성 의존성 주입)

설명에 사용할 예시 코드

온라인 쇼핑몰에서 정률 할인과 정액 할인이 있다고 가정해보자.

그렇다면 다음과 같이 할인 정책 인터페이스와 각각의 구현체가 있을 것이다.

 

아래는 결제를 도와주는 결제 서비스 레이어이다.

PaymentService에서 DiscountPolicy 인터페이스를 Autowired로 의존성 주입 받고 있다.

(참고로 생성자가 하나면 @Autowired를 생략가능하다.)

이 때 @Autowired는 의존성 주입을 하기 위해 내부적으로 해당 객체의 타입 DiscountPolicy로 스프링 컨테이너에서 Bean을 조회한다.

그런데 DiscountPolicy를 구현한 Bean 등록된 구현체는 FlatDiscountPolicy와 RateDiscountPolicy 2개가 있다.

(이미 인텔리제이가 discountPolicy부분에 bean이 2개 이상 있다고 경고를 주고 있다.)

이 때 의존성 주입 시점에서 스프링은 한 개의 Bean을 기대했지만 2개의 Bean이 조회되어 어떤 것을 의존성 주입해야할 지 선택하지 못해 아래와 같은 UnsatisfiedDependencyException 예외가 발생한다.

수동 Bean 등록과 자동 Bean 등록

PaymentService는 컴포넌트 스캔 방식으로 Bean 등록하고 DiscountPolicy는 수동으로 Bean 등록했다.

이렇게 한 이유는 보통 MVC 패턴인 Controller - Service - Repository 구조의 경우에는

프로젝트의 구조가 명확하고, 컴포넌트들이 잘 정의(@Controller, @Service, @Repository)되어 있을 때 사용하면 귀찮게 설정 클래스를 만들어 주입할 대상들을 하나씩 @Bean 등록하고 정의해주지 않아도 되기 때문에 개발 생상성이 올라갈 수 있다.

하지만 DiscountPolicy 같은 경우에는 다형성을 갖고 있고 언제든 할인 정책이 변경될 여지가 있다.

만약 컴포넌트 스캔 방식을 사용해 자동으로 Bean 등록 되도록 했다면 밑에서 설명하겠지만 특정 구현체를 의존성 주입하도록 하기 위해서 변경될 구현체 파일을 열어서 특정 어노테이션을 붙이고 변경되기 전의 파일을 열어 특정 어노테이션을 제거해야한다.

이는 상당히 번거롭다.

그래서 이 경우에는 @Configuration이 붙은 설정 클래스를 만들어 한 눈에 구현체들이 파악되고 정책이 변경되더라도 한 클래스 파일만 열어보면 되므로 수동 Bean 등록하는 방식을 사용했다.

 

Bean이 2개 이상일 때 특정 Bean을 선택하는 방법

조회 되는 Bean이 2개 이상일 때 아래와 같은 방법으로 원하는 Bean을 선택하도록 할 수 있다.

 

@Qualifier

추가 구분자를 붙여주서 Bean을 탐색하게 하는 방법이다.

등록된 Bean 이름을 변경하는 것은 아니다.

 

RateDiscountPolicy가 Bean 등록되고 의존성 주입되어 50% 할인된 5000원이 나오는 결과를 볼 수 있다.

 

@Primary

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

해당 어노테이션이 붙은 Bean이 의존성 주입의 대상이 된다.

 

FlatDiscountPolicy가 Bean 등록되고 의존성 주입되어 1000원이 할인된 9000원이 나오는 결과를 볼 수 있다.

 

다형성을 가진 클래스의 구현체 모두 조회하기

다형성을 갖는 클래스의 구현체의 Bean이 모두 필요한 경우가 있을 수 있다.

이 경우 아래와 같은 방법으로 구현체의 Bean들을 조회할 수 있다.

 

Map 활용 예시

이 경우에는 사용자가 직접 할인 정책을 선택하도록 할 수 있게 만들 수 있다.

Map의 key값으로는 Bean으로 등록되는 이름이 저장된다.

정액을 할인을 선택해 1000원 할인 받은 것을 볼 수 있고,

수동으로 Bean을 등록했기 때문에 메서드명이 Bean이름으로 key에 등록된 것을 볼 수 있다.

 

List 활용 예시

List에 DiscountPolicy의 구현체들이 담긴 것을 볼 수 있다.