Fine-tuning Annotation-based Autowiring with Qualifiers(한글)

기본(또는 비폴백) 후보를 하나만 결정할 수 있는 경우 @Primary 및 `@Fallback`은 여러 인스턴스에서 유형별로 autowiring을 사용하는 효과적인 방법입니다.

선택 프로세스에 대한 더 많은 제어가 필요한 경우 Spring의 @Qualifier 어노테이션을 사용할 수 있습니다. 한정자 값을 특정 인수와 연결하여 각 인수에 대해 특정 Bean이 선택되도록 유형 일치 집합을 좁힐 수 있습니다. 가장 간단한 경우에는 다음 예제와 같이 단순한 설명 값일 수 있습니다:

  • Java

  • Kotlin

public class MovieRecommender {

	@Autowired
	@Qualifier("main")
	private MovieCatalog movieCatalog;

	// ...
}
class MovieRecommender {

	@Autowired
	@Qualifier("main")
	private lateinit var movieCatalog: MovieCatalog

	// ...
}

다음 예시와 같이 개별 생성자 인수 또는 메서드 매개변수에 @Qualifier 어노테이션을 지정할 수도 있습니다:

  • Java

  • Kotlin

public class MovieRecommender {

	private final MovieCatalog movieCatalog;

	private final CustomerPreferenceDao customerPreferenceDao;

	@Autowired
	public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
			CustomerPreferenceDao customerPreferenceDao) {
		this.movieCatalog = movieCatalog;
		this.customerPreferenceDao = customerPreferenceDao;
	}

	// ...
}
class MovieRecommender {

	private lateinit var movieCatalog: MovieCatalog

	private lateinit var customerPreferenceDao: CustomerPreferenceDao

	@Autowired
	fun prepare(@Qualifier("main") movieCatalog: MovieCatalog,
				customerPreferenceDao: CustomerPreferenceDao) {
		this.movieCatalog = movieCatalog
		this.customerPreferenceDao = customerPreferenceDao
	}

	// ...
}

다음 예제는 해당 Bean 정의를 보여줍니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config/>

	<bean class="example.SimpleMovieCatalog">
		<qualifier value="main"/> (1)

		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<qualifier value="action"/> (2)

		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>
1 main 한정자 값을 가진 Bean은 생성자 인수인 이 동일한 값으로 한정된 생성자 인수로 연결됩니다.
2 action 한정자 값을 가진 Bean은 동일한 값으로 한정된 생성자 인수가 이 동일한 값으로 한정된 생성자 인수로 연결됩니다.

Fallback 매치의 경우 Bean 이름이 기본 한정자 값으로 간주됩니다. 따라서 중첩된 한정자 요소 대신 mainid 로 Bean을 정의하여 동일한 일치 결과를 얻을 수 있습니다. 그러나 이 규칙을 사용하여 특정 Bean을 이름으로 참조할 수는 있지만, @Autowired 는 기본적으로 선택적 의미 한정자를 사용한 유형 중심 주입에 관한 것입니다. 즉, 한정자 값은 Bean 이름 폴백을 사용하더라도 유형 일치 집합 내에서 항상 좁은 의미론을 갖습니다. 이들은 고유한 Bean id 에 대한 참조를 의미적으로 표현하지 않습니다. 좋은 한정자 값은 앞의 예와 같이 익명 Bean 정의의 경우 자동 생성될 수 있는 main 또는 EMEA 또는 persistent 로, Bean id 와 독립적인 특정 구성 요소의 특성을 표현합니다.

한정자는 앞서 설명한 것처럼 입력된 컬렉션에도 적용됩니다. — 예를 들어 Set<MovieCatalog>`에 적용됩니다. 이 경우, 선언된 한정자에 따라 일치하는 모든 Bean이 컬렉션으로 주입됩니다. 이는 한정자가 고유할 필요가 없음을 의미합니다. 오히려 필터링 기준을 구성합니다. 예를 들어, 동일한 한정자 값 "`action`"을 가진 여러 개의 `MovieCatalog Bean을 정의할 수 있으며, 이들 모두는 @Qualifier("action") 로 어노테이션이 달린 Set<MovieCatalog> 에 주입됩니다.

한정자 값이 type-matching 후보 내에서 대상 Bean 이름에 대해 선택되도록 하면 주입 지점에서 @Qualifier 어노테이션이 필요하지 않습니다. 다른 resolution indicator(???)(한정자 또는 기본 마커 등)가 없는 경우, 고유하지 않은 종속성 상황에서 Spring은 주입 지점 이름(즉, 필드 이름 또는 매개 변수 이름)을 대상 Bean 이름과 일치시키고 동일한 이름의 후보가 있는 경우 선택합니다.

버전 6.1부터 이를 위해서는 -parameters Java 컴파일러 플래그가 있어야 합니다.

즉, 어노테이션 기반 주입을 이름으로 표현하려는 경우, type-matching 후보 중 Bean 이름으로 선택할 수 있더라도 @Autowired 를 주로 사용하지 마세요. 대신 JSR-250 @Resource 어노테이션을 사용하세요. 이는 고유한 이름으로 특정 대상 컴포넌트를 식별하도록 의미적으로 정의되며, 선언된 유형은 매칭 프로세스와 관련이 없습니다. @Autowired 는 다소 다른 의미를 가집니다: 유형별로 후보 Bean을 선택한 후, 지정된 String 한정자 값은 유형이 선택된 후보 내에서만 고려됩니다(예: 동일한 한정자 레이블로 표시된 Bean에 대해 account 한정자를 일치시키는 경우).

그 자체가 컬렉션, Map 또는 배열 타입으로 정의된 Bean의 경우, 특정 컬렉션이나 배열 Bean을 고유한 이름으로 참조하는 @Resource 가 좋은 해결책입니다. 즉, 4.3부터는 @Bean 반환 타입 서명이나 컬렉션 상속 계층 구조에 요소 타입 정보가 보존되어 있다면 Spring의 @Autowired 타입 매칭 알고리즘을 통해서도 컬렉션, Map 및 배열 타입을 매칭할 수 있습니다. 이 경우 이전 단락에서 설명한 대로 한정자 값을 사용하여 동일한 유형의 컬렉션 중에서 선택할 수 있습니다.

4.3부터 @Autowired 는 주입에 대한 자체 참조(즉, 현재 주입된 Bean을 다시 참조하는 것)도 간주됩니다. 자체 주입은 대체 수단(fallback)이라는 점에 유의하세요. 다른 컴포넌트에 대한 일반 종속성은 항상 우선순위를 갖습니다. 그런 의미에서 자체 참조는 정기적인 후보 선택에 참여하지 않으므로 특히 기본이 되지 않습니다. 오히려 항상 가장 낮은 우선순위를 갖습니다. 실제로는 자체 참조를 최후의 수단으로만 사용해야 합니다(예: Bean의 트랜잭션 프록시를 통해 동일한 인스턴스에서 다른 메서드를 호출하는 경우). 이러한 시나리오에서는 영향을 받는 메서드를 별도의 델리게이트(delegate) Bean으로 분리하는 것을 고려하세요. 또는 현재 Bean의 고유 이름으로 프록시를 다시 가져올 수 있는 @Resource 를 사용할 수 있습니다.

동일한 구성 클래스에서 @Bean 메서드의 결과를 주입하려고 시도하는 것도 사실상 자체 참조 시나리오입니다. 이러한 참조가 실제로 필요한 경우 메서드 서명에서 이러한 참조를 느리게 해결하거나(구성 클래스의 자동 autowiring 필드 대신) 영향을 받는 @Bean 메서드를 static 으로 선언하여 포함된 구성 클래스 인스턴스와 그 수명 주기에서 분리하세요. 그렇지 않으면 이러한 Bean은 폴백 단계에서만 고려되며 다른 구성 클래스에서 일치하는 Bean이 기본 후보로 선택됩니다(사용 가능한 경우).

@Autowired 은 필드, 생성자 및 다중 인수 메서드에 적용되므로 매개변수 수준에서 한정자(@Qualifier) 어노테이션을 통해 범위를 좁힐 수 있습니다. 반면 @Resource 는 단일 인수가 있는 필드 및 Bean 속성 설정자 메서드에만 지원됩니다. 따라서 주입 대상이 생성자 또는 다중 인수 메서드인 경우 한정자를 사용해야 합니다.

사용자 지정 한정자 어노테이션을 직접 만들 수 있습니다. 이렇게 하려면 다음 예시와 같이 어노테이션을 정의하고 정의 내에 @Qualifier 어노테이션을 제공하세요:

  • Java

  • Kotlin

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

	String value();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)

그런 다음 다음 예제와 같이 자동 연결 필드 및 매개변수에 사용자 지정 한정자를 제공할 수 있습니다:

  • Java

  • Kotlin

public class MovieRecommender {

	@Autowired
	@Genre("Action")
	private MovieCatalog actionCatalog;

	private MovieCatalog comedyCatalog;

	@Autowired
	public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
		this.comedyCatalog = comedyCatalog;
	}

	// ...
}
class MovieRecommender {

	@Autowired
	@Genre("Action")
	private lateinit var actionCatalog: MovieCatalog

	private lateinit var comedyCatalog: MovieCatalog

	@Autowired
	fun setComedyCatalog(@Genre("Comedy") comedyCatalog: MovieCatalog) {
		this.comedyCatalog = comedyCatalog
	}

	// ...
}

다음으로, 후보 Bean 정의에 대한 정보를 제공할 수 있습니다. '<qualifier/>` 태그를 <bean/> 태그의 하위 요소로 추가한 다음 사용자 정의 qualifier 어노테이션과 일치하도록 typevalue 를 지정할 수 있습니다. 유형은 어노테이션의 정규화된 클래스 이름과 일치합니다. 또는 이름이 충돌할 위험이 없는 경우 편의를 위해 짧은 클래스 이름을 사용할 수 있습니다. 다음 예에서는 두 가지 접근 방식을 모두 보여줍니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config/>

	<bean class="example.SimpleMovieCatalog">
		<qualifier type="Genre" value="Action"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<qualifier type="example.Genre" value="Comedy"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

클래스경로 스캐닝 및 관리되는 컴포넌트에서 XML로 한정자 메타데이터를 제공하는 대신 어노테이션 기반 대안을 볼 수 있습니다. 구체적으로는 어노테이션으로 한정자 메타데이터 제공을 참조하세요.

경우에 따라 값 없이 어노테이션을 사용하는 것으로 충분할 수 있습니다. 이는 어노테이션이 보다 일반적인 용도로 사용되며 여러 유형의 종속성에 걸쳐 적용될 수 있는 경우에 유용할 수 있습니다. 예를 들어 인터넷에 연결할 수 없을 때 검색할 수 있는 오프라인 카탈로그를 제공할 수 있습니다. 먼저 다음 예시와 같이 간단한 어노테이션을 정의합니다:

  • Java

  • Kotlin

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline

그런 다음 다음 예시와 같이 자동 연결할 필드 또는 속성에 어노테이션을 추가합니다:

  • Java

  • Kotlin

public class MovieRecommender {

	@Autowired
	@Offline (1)
	private MovieCatalog offlineCatalog;

	// ...
}
1 이 줄은 @Offline 어노테이션을 추가합니다.
class MovieRecommender {

	@Autowired
	@Offline (1)
	private lateinit var offlineCatalog: MovieCatalog

	// ...
}
1 이 줄은 @Offline 어노테이션을 추가합니다.

이제 Bean 정의에는 다음 예제와 같이 qualifier type 만 있으면 됩니다:

<bean class="example.SimpleMovieCatalog">
	<qualifier type="Offline"/> (1)
	<!-- inject any dependencies required by this bean -->
</bean>
1 This element specifies the qualifier.

또한 단순한 value 속성에 추가하거나 대신 명명된 속성을 허용하는 사용자 지정 한정자 주석을 정의할 수도 있습니다. 그런 다음 autowiring할 필드 또는 매개변수에 여러 속성 값이 지정된 경우, autowiring 후보로 간주되려면 Bean 정의가 이러한 모든 속성 값과 일치해야 합니다. 예를 들어 다음 어노테이션 정의를 고려해 보겠습니다:

  • Java

  • Kotlin

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

	String genre();

	Format format();
}
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)

이 경우 Format 은 다음과 같이 정의된 열거형입니다:

  • Java

  • Kotlin

public enum Format {
	VHS, DVD, BLURAY
}
enum class Format {
	VHS, DVD, BLURAY
}

autowiring할 필드에는 사용자 정의 한정자가 주석으로 지정되며 두 속성에 대한 값을 포함합니다: genreformat 속성의 값을 포함합니다:

  • Java

  • Kotlin

public class MovieRecommender {

	@Autowired
	@MovieQualifier(format=Format.VHS, genre="Action")
	private MovieCatalog actionVhsCatalog;

	@Autowired
	@MovieQualifier(format=Format.VHS, genre="Comedy")
	private MovieCatalog comedyVhsCatalog;

	@Autowired
	@MovieQualifier(format=Format.DVD, genre="Action")
	private MovieCatalog actionDvdCatalog;

	@Autowired
	@MovieQualifier(format=Format.BLURAY, genre="Comedy")
	private MovieCatalog comedyBluRayCatalog;

	// ...
}
class MovieRecommender {

	@Autowired
	@MovieQualifier(format = Format.VHS, genre = "Action")
	private lateinit var actionVhsCatalog: MovieCatalog

	@Autowired
	@MovieQualifier(format = Format.VHS, genre = "Comedy")
	private lateinit var comedyVhsCatalog: MovieCatalog

	@Autowired
	@MovieQualifier(format = Format.DVD, genre = "Action")
	private lateinit var actionDvdCatalog: MovieCatalog

	@Autowired
	@MovieQualifier(format = Format.BLURAY, genre = "Comedy")
	private lateinit var comedyBluRayCatalog: MovieCatalog

	// ...
}

마지막으로, Bean 정의에는 일치하는 한정자(qualifier) 값이 포함되어야 합니다. 이 예에서는 <qualifier/> 요소 대신 Bean 메타 속성을 사용할 수 있음을 보여줍니다. 가능한 경우 <qualifier/> 요소와 해당 속성이 우선하지만, 다음 예제의 마지막 두 Bean 정의에서와 같이 해당 한정자가 없는 경우 자동 와이어링 메커니즘은 <meta/> 태그 내에 제공된 값으로 되돌아갑니다:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context
		https://www.springframework.org/schema/context/spring-context.xsd">

	<context:annotation-config/>

	<bean class="example.SimpleMovieCatalog">
		<qualifier type="MovieQualifier">
			<attribute key="format" value="VHS"/>
			<attribute key="genre" value="Action"/>
		</qualifier>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<qualifier type="MovieQualifier">
			<attribute key="format" value="VHS"/>
			<attribute key="genre" value="Comedy"/>
		</qualifier>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<meta key="format" value="DVD"/>
		<meta key="genre" value="Action"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

	<bean class="example.SimpleMovieCatalog">
		<meta key="format" value="BLURAY"/>
		<meta key="genre" value="Comedy"/>
		<!-- inject any dependencies required by this bean -->
	</bean>

</beans>