본문 바로가기

Spring

Spring- 중복되는 빈이 있는 경우, 의존 관계 자동 주입의 여러가지 방법

빈(타입)이 중복되는 경우

  - 사용할 기본 코드

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으로 받는 것 빼고는 나머지 클래스들과 동일하다. 테스트 코드 부분도 다른 테스트 코드들과 거의 흡사하다. 단지 의존관계 자동 주입의 방법이 여러가지가 있다는 것을 확인하기위해 중복되더라도 여러가지 방법을 사요한 것 뿐이다.

 

빈 중복 시 의존관계 주입 방법

  1. @Primary
  2. 파라미터 명을 넣고자 하는 빈 이름으로 등록
  3. @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