Fine-tuning Annotation-based Autowiring with Qualifiers

@Primary@Fallback 是在有多个实例时按类型使用自动装配有用的方法,此时可以确定一个主(或非回退)候选对象。

@Primary and @Fallback are effective ways to use autowiring by type with several instances when one primary (or non-fallback) candidate can be determined.

当您需要对选择过程进行更多控制时,可以使用 Spring 的 @Qualifier 注释。您可以将限定符值与特定参数关联,缩小类型匹配的集合,以便为每个参数选择一个特定的 bean。在最简单的情况下,这可以是一个简单的描述性值,如下例所示:

When you need more control over the selection process, you can use Spring’s @Qualifier annotation. You can associate qualifier values with specific arguments, narrowing the set of type matches so that a specific bean is chosen for each argument. In the simplest case, this can be a plain descriptive value, as shown in the following example:

  • Java

  • Kotlin

public class MovieRecommender {

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

	// ...
}
class MovieRecommender {

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

	// ...
}

您还可以在单独的构造函数参数或方法参数上指定 @Qualifier 注释,如下例所示:

You can also specify the @Qualifier annotation on individual constructor arguments or method parameters, as shown in the following example:

  • 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 定义。

The following example shows corresponding bean definitions.

<?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 The bean with the main qualifier value is wired with the constructor argument that is qualified with the same value.
2 The bean with the action qualifier value is wired with the constructor argument that is qualified with the same value.

对于回退匹配,bean 名称被视为默认限定符值。因此,您可以为 bean 定义一个 idmain 而不是嵌套限定符元素,从而得到同样的匹配结果。然而,尽管您可以使用此约定按名称引用特定 bean,但 @Autowired 从根本上是关于类型驱动的注入,带有可选的语义限定符。这意味着限定符值,即使使用 bean 名称回退,在类型匹配集中也始终具有缩小语义。它们在语义上不表示对唯一 bean id 的引用。好的限定符值是 mainEMEApersistent,它们表示特定组件的特性,这些特性不依赖于 bean id,在匿名 bean 定义(例如前面的示例中的定义)的情况下,该 id 可能是自动生成的。

For a fallback match, the bean name is considered a default qualifier value. Thus, 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.

限定符也适用于类型化集合,如前所述,例如 Set<MovieCatalog>。在这种情况下,所有匹配的 bean(根据声明的限定符)都会作为集合注入。这意味着限定符不必是唯一的。相反,它们构成过滤条件。例如,您可以使用相同限定符值“action”定义多个 MovieCatalog bean,所有这些 bean 都被注入到使用 @Qualifier("action") 注释的 Set<MovieCatalog> 中。

Qualifiers also apply to typed collections, as discussed earlier — for example, to Set<MovieCatalog>. In this case, all matching beans, according to the declared qualifiers, are injected as a collection. This implies that qualifiers do not have to be unique. Rather, they constitute filtering criteria. For example, you can define multiple MovieCatalog beans with the same qualifier value “action”, all of which are injected into a Set<MovieCatalog> annotated with @Qualifier("action").

让限定符值在目标 bean 名称内进行选择,在类型匹配候选对象内进行选择时,不需要在注入点处进行 @Qualifier 注释。如果没有其他解析指示符(例如限定符或主标记),对于非唯一依赖情况,Spring 会将注入点名称(即字段名称或参数名称)与目标 bean 名称进行匹配,并选择同名候选对象(如果有的话) (按 bean 名称或关联别名)。

Letting qualifier values select against target bean names, within the type-matching candidates, does not require a @Qualifier annotation at the injection point. If there is no other resolution indicator (such as a qualifier or a primary marker), for a non-unique dependency situation, Spring matches the injection point name (that is, the field name or parameter name) against the target bean names and chooses the same-named candidate, if any (either by bean name or by associated alias).

从版本 6.1 开始,这需要存在 -parameters Java 编译器标志。从 6.2 开始,容器对 bean 名称匹配应用快速的快捷解析,当参数名称与 bean 名称匹配且没有任何类型、限定符或主条件覆盖该匹配时,将绕过完整的类型匹配算法。因此,建议您的参数名称与目标 bean 名称相匹配。

Since version 6.1, this requires the -parameters Java compiler flag to be present. As of 6.2, the container applies fast shortcut resolution for bean name matches, bypassing the full type matching algorithm when the parameter name matches the bean name and no type, qualifier or primary conditions override the match. It is therefore recommendable for your parameter names to match the target bean names.

作为按名称注入的替代方法,请考虑 JSR-250 @Resource 注释,该注释在语义上被定义为通过其唯一名称识别特定目标组件,所声明的类型与匹配过程无关。@Autowired 有不同的语义:在按类型选择候选 bean 后,指定的 String 限定符值仅在那些类型选择的候选对象中被考虑(例如,将 account 限定符与标记了相同限定符标签的 bean 进行匹配)。

As an alternative for injection by name, consider the JSR-250 @Resource annotation which is semantically defined to identify a specific target component by its unique name, with the declared type being irrelevant for the matching process. @Autowired has rather different semantics: after selecting candidate beans by type, the specified String qualifier value is considered within those type-selected candidates only (for example, matching an account qualifier against beans marked with the same qualifier label).

对于本身被定义为集合、Map 或数组类型的 bean,@Resource 是很好的解决方案,它通过唯一名称引用特定的集合或数组 bean。也就是说,您还可以通过 Spring 的 @Autowired 类型匹配算法匹配集合、Map 和数组类型,只要元素类型信息保留在 @Bean 返回类型签名或集合继承层次结构中即可。在这种情况下,您可以使用限定符值在类型相同的集合中选择,如前一段所述。

For beans that are themselves defined as a collection, Map, or array type, @Resource is a fine solution, referring to the specific collection or array bean by unique name. That said, you can match collection, Map, and array types through Spring’s @Autowired type matching algorithm as well, as long as the element type information is preserved in @Bean return type signatures or collection inheritance hierarchies. In this case, you can use qualifier values to select among same-typed collections, as outlined in the previous paragraph.

@Autowired 还考虑了自引用以进行注入(即引用回当前正在注入的 bean)。请注意,自注入是回退。对其他组件的常规依赖始终具有优先级。从这个意义上说,自引用不参与常规候选选择,因此特别是非主要的。相反,它们始终以最低优先级结束。在实践中,您应该仅将自引用作为最后的手段(例如,通过 bean 的事务代理调用同一实例上的其他方法)。在这种情况下,请考虑将受影响的方法分解到一个单独的委托 bean 中。或者,您可以使用 @Resource,它可以通过其唯一名称获得代理回到当前 bean。

@Autowired also considers self references for injection (that is, references back to the bean that is currently injected). Note that self injection is a fallback. Regular dependencies on other components always have precedence. In that sense, self references do not participate in regular candidate selection and are therefore in particular never primary. On the contrary, they always end up as lowest precedence. In practice, you should use self references as a last resort only (for example, for calling other methods on the same instance through the bean’s transactional proxy). Consider factoring out the affected methods to a separate delegate bean in such a scenario. Alternatively, you can use @Resource, which may obtain a proxy back to the current bean by its unique name.

尝试注入来自同一配置类的 @Bean 方法的结果实际上也是一个自引用场景。在此方法签名中(与配置类中的自动装配字段相反),以惰性方式解析此类引用,或者将受影响的 @Bean 方法声明为 static,将其与包含的配置类实例及其生命周期分离。否则,此类 bean 仅在回退阶段被考虑,而其他配置类上的匹配 bean 则被选为主候选对象(如果可用的话)。

Trying to inject the results from @Bean methods on the same configuration class is effectively a self-reference scenario as well. Either lazily resolve such references in the method signature where it is actually needed (as opposed to an autowired field in the configuration class) or declare the affected @Bean methods as static, decoupling them from the containing configuration class instance and its lifecycle. Otherwise, such beans are only considered in the fallback phase, with matching beans on other configuration classes selected as primary candidates instead (if available).

@Autowired 适用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释缩小范围。相比之下,@Resource 仅支持带有单个参数的字段和 bean 属性 setter 方法。因此,如果您的注入目标是构造函数或多参数方法,您应该坚持使用限定符。

@Autowired applies to fields, constructors, and multi-argument methods, allowing for narrowing through qualifier annotations at the parameter level. In contrast, @Resource is supported only for fields and bean property setter methods with a single argument. As a consequence, you should stick with qualifiers if your injection target is a constructor or a multi-argument method.

您可以创建自己的自定义限定符注释。为此,请定义注释并在定义中提供 @Qualifier 注释,如下例所示:

You can create your own custom qualifier annotations. To do so, define an annotation and provide the @Qualifier annotation within your definition, as the following example shows:

  • 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)

然后,您可以对自动装配的字段和参数提供自定义限定符,如下例所示:

Then you can provide the custom qualifier on autowired fields and parameters, as the following example shows:

  • 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/> 标记的子元素,然后指定 typevalue 以匹配您的自定义限定符注释。该类型与注释的完全限定类名进行匹配。或者,如果不存在冲突名称的风险,则可以将简短的类名作为一个方便的方法使用。以下示例演示了这两种方法:

Next, you can provide the information for the candidate bean definitions. You can add <qualifier/> tags as sub-elements of the <bean/> tag and then specify the type and value to match your custom qualifier annotations. The type is matched against the fully-qualified class name of the annotation. Alternately, as a convenience if no risk of conflicting names exists, you can use the short class name. The following example demonstrates both approaches:

<?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>

在 "@44" 中,你可以看到基于注解的替代方式,用于在 XML 中提供限定符元数据。具体来说,请参见 "@45"。

In Classpath Scanning and Managed Components, you can see an annotation-based alternative to providing the qualifier metadata in XML. Specifically, see Providing Qualifier Metadata with Annotations.

在某些情况下,使用没有值的注释就足够了。当注释服务于更通用目的并且可以应用于多种不同类型的依赖项时,这很有用。例如,您可以提供离线目录,在没有互联网连接时可以进行搜索。首先,定义简单的注释,如下例所示:

In some cases, using an annotation without a value may suffice. This can be useful when the annotation serves a more generic purpose and can be applied across several different types of dependencies. For example, you may provide an offline catalog that can be searched when no Internet connection is available. First, define the simple annotation, as the following example shows:

  • 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

然后,向需要自动注入的字段或属性添加注释,如下例所示:

Then add the annotation to the field or property to be autowired, as shown in the following example:

Java
public class MovieRecommender {

	@Autowired
	@Offline (1)
	private MovieCatalog offlineCatalog;

	// ...
}
1 This line adds the @Offline annotation.
Kotlin
class MovieRecommender {

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

	// ...
}
2 This line adds the @Offline annotation.

现在,bean 定义只需要 type 限定符,如以下示例所示:

Now the bean definition only needs a qualifier type, as shown in the following example:

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

您还可以定义接受除简单 value 属性之外或替代 simple value 属性的命名属性的自定义限定符注释。如果在要自动注入的字段或参数上指定多个属性值,则 bean 定义必须匹配所有这些属性值,才能被视为自动注入候选者。作为一个示例,考虑以下注释定义:

You can also define custom qualifier annotations that accept named attributes in addition to or instead of the simple value attribute. If multiple attribute values are then specified on a field or parameter to be autowired, a bean definition must match all such attribute values to be considered an autowire candidate. As an example, consider the following annotation definition:

  • 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 是一个枚举,定义如下:

In this case Format is an enum, defined as follows:

  • Java

  • Kotlin

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

自动注入的字段使用自定义限定符进行注释,并包含两个属性的值:genreformat,如下例所示:

The fields to be autowired are annotated with the custom qualifier and include values for both attributes: genre and format, as the following example shows:

  • 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 定义应包含匹配的限定符值。此示例还演示了如何使用 Bean 元属性替代`<qualifier/>` 元素。如果存在,<qualifier/> 元素及其属性优先,但如果不存在此类限定符(如下例中最后两个 bean 定义那样),自动注入机制将回退到`<meta/>` 标记中提供的值:

Finally, the bean definitions should contain matching qualifier values. This example also demonstrates that you can use bean meta attributes instead of the <qualifier/> elements. If available, the <qualifier/> element and its attributes take precedence, but the autowiring mechanism falls back on the values provided within the <meta/> tags if no such qualifier is present, as in the last two bean definitions in the following example:

<?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>