@Autowired field match, @Qualifier, @Primary

namkun·2022년 3월 21일
0

Spring

목록 보기
12/18

해당 내용은 '스프링 입문을 위한 자바 객체 지향의 원리와 이해'와 인프런 김영한님의 '스프링 핵심 원리 - 기본편' 강의를 참고하였습니다.


조회되는 빈이 2개 이상인 경우에는 어떻게 원하는 빈을 지정할까?

앞서 만들었던 클래스 구조에서 확인해보자.

우리는 FuelTank라는 인터페이스를 만들고,

ElectricBattery, GasolineOil 이라는 위의 인터페이스를 상속받은 클래스를 만들었다.

그런데 만약 우리가 위 2개의 클래스에 모두 @Componet 어노테이션을 달아줬다면 어떻게 될까?

이렇게 다 붙여보자.

ElectricBattery.java

@Component
public class ElectricBattery implements FuelTank {
...

GasolineOil.java

@Component
public class GasolineOil implements FuelTank{
...

자 이제 테스트를 돌려보자.

import static org.assertj.core.api.Assertions.assertThat;

public class AutoAppConfigTest {

    @Test
    void basicScan(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AutoApplicationConfig.class);
        CarImpl carImpl = ac.getBean(CarImpl.class);
        assertThat(carImpl).isInstanceOf(CarImpl.class);
    }
}

그럼 이제 다음과 같이 에러를 만날 수 있을 것이다.

Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'hello.core.carTest.tank.FuelTank' available: expected single matching bean but found 2: electricBattery,gasolineOil

(에러 메세지가 너무 친절하다...)

싱글 빈이 2개가 발견 되었다고 나온다.

이런 경우 스프링 빈을 수동등록 해도 해결은 되지만, 의존 관계 자동주입에서 해결하는 방법이 있다.

@Autowired 필드 명 매칭

@Autowired는 타입 매칭을 시도하고, 이때 여러 빈이 존재하면 필드 이름으로 빈 이름을 추가 매칭한다.

아까 에러가 발생한 곳을 찾아가보자.

AutoApplicationConfig.java

@ComponentScan(
        basePackages = "hello.core.carTest",
        excludeFilters = {
        @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Configuration.class) // 기존에 만든 다른 config 클래스가 빈으로 등록되는 것을 막기 위해서 제외시킨다.
})
public class AutoApplicationConfig {
}

여기서 에러가 발생하는데, 아마 위의 @ComponentScan을 하면서 에러가 발생한 것 같다. 그러니 우리는 FuelTank가 생성자에 들어간 모든 곳에 우리가 사용하고자 하는 클래스 이름으로 바꿔주면 될 것이다.

이번엔 electricBattery를 쓴다고 해보자. 그럼 다음과 같이 바꿔주면 될 것이다.

CarImpl.java

...
    @Autowired
    public CarImpl(FuelTank electricBattery) {
        this.fuelTank = electricBattery;
    }
...

FixServiceImpl.java

...
    @Autowired
    public FixServiceImpl(FuelTank electricBattery, Transmission transmission) {
        this.fuelTank = electricBattery;
        this.transmission = transmission;
    }
...

자 이제 테스트를 돌려보면... 별 이슈 없이 잘 돌아갈 것이다.

@Autowired의 필드 명 매칭은 우선적으로 타입 매칭을 한 뒤, 그 결과 여러 빈이 있는 경우에만 추가 작동하는 것이다.

@Qualifier

@Qualifier는 추가 구분자를 붙여주는 방법이다.

이는 주입시에 추가적인 방법을 제공하는 것이지, 빈 이름을 변경하는 것은 아니다.

우선 각 컴포넌트에 다음과 같이 @Qualifier 어노테이션을 붙여준다.

ElectricBattery.java

@Component
@Qualifier("eletricBattery")
public class ElectricBattery implements FuelTank {
    ...

GasolineOil.java

@Component
@Qualifier("gasolineOil")
public class GasolineOil implements FuelTank{
    ...
    

자 그 다음에 사용되는 곳에 다음과 같이 적어준다.

CarImpl.java

...
    @Autowired
    public CarImpl(@Qualifier("eletricBattery") FuelTank fuelTank) {
        this.fuelTank = fuelTank;
    }
...

FixServiceImpl.java

...
    @Autowired
    public FixServiceImpl(@Qualifier("eletricBattery") FuelTank fuelTank, Transmission transmission) {
        this.fuelTank = fuelTank;
        this.transmission = transmission;
    }
...

위의 자동 주입 받는 곳에서는 @Qualifier가 있는 것을 확인하고, 해당 @Qualifier의 이름에 맞는 것을 찾아서 주입시켜준다.

만약에, 등록하지 않은 @Qualifier를 적으면 어떻게 될까?

그럼 등록하지 않은 @Qualifier의 이름을 갖는 스프링 빈을 찾는다. (@Autowired 필드명 매칭 처럼!)

@Primary

@Primary는 우선순위를 정하는 어노테이션 기법이다.

@Autowired시에 여러 번 매칭되면, @Primary를 갖는 빈이 우선권을 갖는다.

앞선 방법에 비교하면 훨씬 간단하다. (클래스에 어노테이션 하나만 붙이면 되니까..)

그렇기에, 뭔가 자주 사용하는 빈에는 @Primary를, 자주 사용하지 않는 빈은 @Qualifier를 사용해서 매칭하는 경우가 대부분이다.

(이 의미는, @Primary@Qualifier가 둘 다 존재하면, 매칭되는 @Qualifier가 우선권이 더 높다는 의미이다.)

profile
개발하는 중국학과 사람

0개의 댓글