Back-End/Spring

[Spring] Annotation을 이용한 DI (2) @AutoWired와 @Qualifier

유자맛바나나 2022. 2. 2. 14:47

@Autowired 의존성 주입 규칙1: Type

Client 객체: Car

package com.test.ui;

@Component
// @Component("carBean") // id를 직접 입력할 때
public class Car {
    private PowerUnit powerUnit; // Dependency
    
    @Autowired    
    public Car(PowerUnit powerUnit) {
        this.powerUnit = powerUnit; // 생성자를 통한 외부 주입(Injection)
    }
}

의존 객체: ElectricMotor

@Component
// @Component("electricMotorBean") // id를 직접 명시 할때
public class ElectricMotor implements PowerUnit {
    @Override
    public void printPowerType() {
        system.out.println("ElectricMotor");
    }
}
  • @Autowired 어노테이션이 있을 때 Spring이 IoC 컨테이너에서 주입할 의존 Bean을 찾는 첫 번째 조건은 Type이다
  • 위 예제 코드에서 의존 객체 powerUnit의 Type이란 PowerUnit 인터페이스가 된다. Spring은 PowerUnit의 구현체인 ElectricMotor를 IoC 컨테이너에서 찾는다.

만약 의존 객체 PowerUnit의 구현체가 여러개라면 Spring은 어떤 Bean 객체를 주입해야할까?
의존 객체2: GasolineEngine

@Component
public class GasolineEngine implements PowerUnit {
    @Override
    public void printPowerType() {
        system.out.println("GasolineEngine");
    }
}

위와 같이 인터페이스 PowerUnit의 새로운 구현체 GasolineEngine가 추가되어 PowerUnit의 Type의 주입 가능한 객체 두 개가 있다면 Spring은 둘 중 어떤 bean을 주입할까? 정답은 Exception 발생이다.

Exception 내용

Description:
Parameter 0 of constructor in com.hyppeople.hyppeople.service.MemberService required a single bean, but 2 were found:
→ 하나의(single) bean이 필요하지만 2개가 발견됐다.

Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
→ @Qualifier를 이용해 bean을 구분할 수 있도록 해라


이러한 문제는 qualifier 속성과 @Qualifier를 사용해 해결할 수 있다.


@Autowired 의존성 주입 규칙2: qualifier 속성과 @Qualifier

Qualify와 Qualifier는 각각 사전적으로 아래와 같은 뜻을 가진다. 즉, 주입 가능한 여러 개의 의존 객체 중 실제 주입이 될 하나의 객체를 선정하는데 사용되는 것으로 예상할 수 있다

  • Qualify: 자격을 얻다
  • Qualifier: 예선 통과자

1) 의존 객체에 @Qualifier 적용하기

의존 객체1: ElectricMotor

@Component
@Qualifier("electric") // Qualifier 명시
public class ElectricMotor implements PowerUnit {
    @Override
    public void printPowerType() {
        system.out.println("ElectricMotor");
    }
}

의존 객체2: GasolineEngine

@Component
@Qualifier("gasoline") // Qualifier 명시
public class GasolineEngine implements PowerUnit {
    @Override
    public void printPowerType() {
        system.out.println("GasolineEngine");
    }
}

  • 의존 객체 Class명 위에 @Qualifier("구분자 이름")와 같이 의존 객체를 구분할 수 있도록 구분자를 명시해준다

2) Client 객체에 @Qualifier 적용하기

Client 객체: Car

package com.test.ui;

@Component
public class Car {
    private PowerUnit powerUnit; 
    
    @Autowired    
    public Car(@Qualifier("electric") PowerUnit powerUnit) { // Qualifier로 주입할 의존 객체 명시
        this.powerUnit = powerUnit; 
    }
}
  • 주입할 의존 객체를 명시하기 위해 Type(PowerUnit) 앞에 명시해준다.
  • 중요!! @Qualifier의 위치는 Parameter 앞에 명시한다.

[참고] Qualifier 속성 대신 id 사용

참고로 qualifer 속성 대신 id를 사용해도 같은 결과를 얻을 수 있다. 다만 Spring 공식 문서에서는 qualifier 사용을 권장하고 있다.

1.9.4. Fine-tuning Annotation-based Autowiring with Qualifiers

You can define the bean with an id of main instead of the nested qualifier element, leading to the same matching result. However, although you can use this convention to refer to specific beans by name, @Autowired is fundamentally about type-driven injection with optional semantic qualifiers. This means that qualifier values, even with the bean name fallback, always have narrowing semantics within the set of type matches. They do not semantically express a reference to a unique bean id. Good qualifier values are main or EMEA or persistent, expressing characteristics of a specific component that are independent from the bean id, which may be auto-generated in case of an anonymous bean definition such as the one in the preceding example.

[참고] xml 형식의 Spring Bean Configuration에서 Qualifier 사용하기

<--------------------------------->
<--                             -->
<--    Example                  -->
<--    SpringConfig.xml 13.3    -->
<--                             -->
<--------------------------------->

<bean class="com.test.di.ElectricMotor">
    <qualifier value="electric"/>
</bean>

<bean class="com.test.di.GasolineEngine">
    <qualifier value="gasoline"/>
</bean>

  • <bean> 태그 내 <qualifier>를 정의한다
  • value 속성에 의존 객체의 구분자를 명시한다.