빈(타입)이 중복되는 경우
- 사용할 기본 코드
package hello.core.beanoverlapping;
public interface Toy { }
package hello.core.beanoverlapping;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
@Component
@Primary
@Qualifier("robotToy")
public class Robot implements Toy{ }
package hello.core.beanoverlapping;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component
@Qualifier("carToy")
public class Car implements Toy{ }
클래스 Robot과 Car는 인터페이스 Toy를 공통적으로 상속한다. Robot과 Car는 모두 스프링 빈으로 등록되어 있고
Robot은 @Primary, @Qualifier("robotToy")가 선언되었다.
Car는 @Qualifier("carToy")가 선언되었다.
테스트
- 구성정보 클래스
@Configuration
@ComponentScan(basePackages = "hello.core.beanoverlapping")
public static class BeanOverLappingConfig{
}
BeanOverLappingConfig 클래스를 구성정보 클래스로 등록했다.
- 테스트 코드
package hello.core.beanoverlapping;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import static org.assertj.core.api.Assertions.*;
public class BeanOverLappingTest {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(BeanOverLappingConfig.class);
// 테스트 코드 시작 //
@Test
@DisplayName("@Primary로 Robot 받기")
void primaryRobot(){
PrimaryRobot bean = ac.getBean(PrimaryRobot.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Robot.class);
assertThat(toy).isNotInstanceOf(Car.class);
}
@Test
@DisplayName("파라미터 명으로 Robot 받기")
void parameterRobot(){
ParameterRobot bean = ac.getBean(ParameterRobot.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Robot.class);
assertThat(toy).isNotInstanceOf(Car.class);
}
@Test
@DisplayName("파라미터 명으로 Car 받기")
void parameterCar(){
ParameterCar bean = ac.getBean(ParameterCar.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Car.class);
assertThat(toy).isNotInstanceOf(Robot.class);
}
@Test
@DisplayName("@Qualfier로 Robot 받기")
void qualifierRobot(){
QualifierRobot bean = ac.getBean(QualifierRobot.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Robot.class);
assertThat(toy).isNotInstanceOf(Car.class);
}
@Test
@DisplayName("@Qualifier로 Car 받기")
void qualifierCar(){
QualifierCar bean = ac.getBean(QualifierCar.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Car.class);
assertThat(toy).isNotInstanceOf(Robot.class);
}
// 테스트 코드 끝 //
}
사실 이 코드내에는 아주 살짝씩만 다르고 중복되는 부분이 굉장히 많다. ParameterRobot의 경우 멤버변수 Toy 객체를 생성자에서 Toy Robot으로 받는 것 빼고는 나머지 클래스들과 동일하다. 테스트 코드 부분도 다른 테스트 코드들과 거의 흡사하다. 단지 의존관계 자동 주입의 방법이 여러가지가 있다는 것을 확인하기위해 중복되더라도 여러가지 방법을 사요한 것 뿐이다.
빈 중복 시 의존관계 주입 방법
- @Primary
- 파라미터 명을 넣고자 하는 빈 이름으로 등록
- @Qualifier 사용
@Primary
@Component
static class PrimaryRobot{
private final Toy toy;
//@Primary로 Robot 받기
@Autowired
public PrimaryRobot(Toy toy){
this.toy = toy;
}
public Toy getToy() {
return toy;
}
}
PrimaryRobot 클래스를 보면 Toy 인스턴스로 robot이 들어온다. car 대신 robot이 들어오는 이유는 Robot 클래스에 @Primary 선언이 되어 car 보다 robot이 DI 시 우선순위를 가지기 때문이다.
파라미터 명으로 매칭
@Component
static class ParameterRobot{
private final Toy toy;
//파라미터 명으로 Robot 받기
@Autowired
public ParameterRobot(Toy robot){
this.toy = robot;
}
public Toy getToy() {
return toy;
}
}
@Component
static class ParameterCar{
private final Toy toy;
//파라미터 명으로 car 받기
@Autowired
public ParameterCar(Toy car){
this.toy = car;
}
public Toy getToy() {
return toy;
}
}
ParameterRobot은 매개변수 명이 robot이고, ParameterCar는 매개변수 명이 car이다. 이처럼 생성자의 매개 변수명을 특정 스프링 빈 이름으로 등록하면 빈이 중복되는 경우 이름이 같은 빈을 찾아와 등록한다.
@Qualifier
@Component
static class QualifierRobot{
private final Toy toy;
//@Qualifier("robotToy")로 Robot 받기
@Autowired
public QualifierRobot(@Qualifier("robotToy")Toy car){
this.toy = car;
}
public Toy getToy() {
return toy;
}
}
@Component
static class QualifierCar{
private final Toy toy;
//@Qualifier("carToy")로 car 받기
@Autowired
public QualifierCar(@Qualifier("carToy")Toy car){
this.toy = car;
}
public Toy getToy() {
return toy;
}
}
생성자 매개변수 앞에 @Qualifer("robotToy"), @Qualifier("carToy")를 붙여 어떤 빈을 받을지 지정할 수 있다.
참고)
테스트 코드를 실행하면 파라미터 명으로 Car를 받는 테스트 케이스가 정상적으로 작동이 되지 않을 수 있는데, 이는 테스트 코드가 잘못된 것이 아니라 Robot, Car 클래스에 어노테이션 @Primary, @Qualifier와 파라미터 명으로 인자를 받는 부분이 서로 충돌을 일으켜서 파라미터 명으로 들어오는 게 아니라 어노테이션 때문에 Robot이 들어오기 때문으로 보인다. 실제로 어노테이션 지우고 ParameterCar() 테스트 케이스만 따로 실행하면 성공한다.
실제 전체 코드
package hello.core.beanoverlapping;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.*;
import org.springframework.stereotype.Component;
import static org.assertj.core.api.Assertions.*;
public class BeanOverLappingTest {
AnnotationConfigApplicationContext ac =
new AnnotationConfigApplicationContext(BeanOverLappingConfig.class);
@Test
@DisplayName("@Primary로 Robot 받기")
void primaryRobot(){
PrimaryRobot bean = ac.getBean(PrimaryRobot.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Robot.class);
assertThat(toy).isNotInstanceOf(Car.class);
}
@Test
@DisplayName("파라미터 명으로 Robot 받기")
void parameterRobot(){
ParameterRobot bean = ac.getBean(ParameterRobot.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Robot.class);
assertThat(toy).isNotInstanceOf(Car.class);
}
@Test
@DisplayName("파라미터 명으로 Car 받기")
void parameterCar(){
ParameterCar bean = ac.getBean(ParameterCar.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Car.class);
//assertThat(toy).isNotInstanceOf(Robot.class);
}
@Test
@DisplayName("@Qualfier로 Robot 받기")
void qualifierRobot(){
QualifierRobot bean = ac.getBean(QualifierRobot.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Robot.class);
assertThat(toy).isNotInstanceOf(Car.class);
}
@Test
@DisplayName("@Qualifier로 Car 받기")
void qualifierCar(){
QualifierCar bean = ac.getBean(QualifierCar.class);
Toy toy = bean.getToy();
System.out.println("toy = " + toy);
assertThat(toy).isInstanceOf(Car.class);
assertThat(toy).isNotInstanceOf(Robot.class);
}
@Component
static class PrimaryRobot{
private final Toy toy;
//@Primary로 Robot 받기
@Autowired
public PrimaryRobot(Toy toy){
this.toy = toy;
}
public Toy getToy() {
return toy;
}
}
@Component
static class ParameterRobot{
private final Toy toy;
//파라미터 명으로 Robot 받기
@Autowired
public ParameterRobot(Toy robot){
this.toy = robot;
}
public Toy getToy() {
return toy;
}
}
@Component
static class ParameterCar{
private final Toy toy;
//파라미터 명으로 car 받기
@Autowired
public ParameterCar(Toy car){
this.toy = car;
}
public Toy getToy() {
return toy;
}
}
@Component
static class QualifierRobot{
private final Toy toy;
//@Qualifier("robotToy")로 Robot 받기
@Autowired
public QualifierRobot(@Qualifier("robotToy")Toy car){
this.toy = car;
}
public Toy getToy() {
return toy;
}
}
@Component
static class QualifierCar{
private final Toy toy;
//@Qualifier("carToy")로 car 받기
@Autowired
public QualifierCar(@Qualifier("carToy")Toy car){
this.toy = car;
}
public Toy getToy() {
return toy;
}
}
@Configuration
@ComponentScan(basePackages = "hello.core.beanoverlapping")
public static class BeanOverLappingConfig{
}
}
'Spring' 카테고리의 다른 글
Spring- 빈 생명 주기 (0) | 2023.02.02 |
---|---|
Spring- 조회한 빈이 모두 필요할 때(Map, List) (0) | 2023.01.31 |
Spring- lombok (0) | 2023.01.31 |
Spring- 옵션 선택 (0) | 2023.01.29 |
Spring- 의존관계 자동 주입 (0) | 2023.01.29 |