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 定义一个 id
为 main
而不是嵌套限定符元素,从而得到同样的匹配结果。然而,尽管您可以使用此约定按名称引用特定 bean,但 @Autowired
从根本上是关于类型驱动的注入,带有可选的语义限定符。这意味着限定符值,即使使用 bean 名称回退,在类型匹配集中也始终具有缩小语义。它们在语义上不表示对唯一 bean id
的引用。好的限定符值是 main
、EMEA
或 persistent
,它们表示特定组件的特性,这些特性不依赖于 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 名称内进行选择,在类型匹配候选对象内进行选择时,不需要在注入点处进行 Letting qualifier values select against target bean names, within the type-matching
candidates, does not require a 从版本 6.1 开始,这需要存在 Since version 6.1, this requires the |
作为按名称注入的替代方法,请考虑 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.
尝试注入来自同一配置类的 Trying to inject the results from |
@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/>
标记的子元素,然后指定 type
和 value
以匹配您的自定义限定符注释。该类型与注释的完全限定类名进行匹配。或者,如果不存在冲突名称的风险,则可以将简短的类名作为一个方便的方法使用。以下示例演示了这两种方法:
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.
|
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
}
自动注入的字段使用自定义限定符进行注释,并包含两个属性的值:genre
和 format
,如下例所示:
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>