使用限定符微调基于注解的自动装配
@Primary
和 @Fallback
是在多实例情况下通过类型自动装配的有效方式,当可以确定一个主要(或非后备)候选者时使用。
当您需要对选择过程有更多控制时,可以使用 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>
具有
main
限定符值的 bean 与具有相同限定符值的构造函数参数连接。具有
action
限定符值的 bean 与具有相同限定符值的构造函数参数连接。
对于回退匹配,bean 名称被视为默认限定符值。因此,你可以定义一个 id
为 main
的 bean 来代替嵌套的限定符元素,从而达到相同的匹配结果。然而,尽管你可以使用这种约定通过名称引用特定的 bean,@Autowired
本质上是关于基于类型的注入,并带有可选的语义限定符。这意味着限定符值,即使是 bean 名称回退,始终在类型匹配集合中具有缩小语义。它们并不在语义上表示对唯一 bean id
的引用。好的限定符值是 main
或 EMEA
或 persistent
,它们表达特定组件的特征,这些特征独立于 bean id
,而 bean id
在匿名 bean 定义的情况下可能是自动生成的,例如前面的例子中的情况。
限定符也适用于类型化集合,如前所述 — 例如,对于 Set<MovieCatalog>
。在这种情况下,所有符合声明的限定符的 bean 都会作为一个集合注入。这意味着限定符不必是唯一的。相反,它们构成了过滤标准。例如,你可以定义多个具有相同限定符值“action”的 MovieCatalog
bean,所有这些 bean 都会被注入到用 @Qualifier("action")
注释的 Set<MovieCatalog>
中。
让限定符值选择目标 bean 名称(在类型匹配候选者中)不需要在注入点使用 @Qualifier
注解。如果没有其他解析指示器(例如限定符或主要标记),对于非唯一依赖情况,Spring 会将注入点名称(即字段名称或参数名称)与目标 bean 名称进行匹配,并选择同名候选者(如果有的话,无论是通过 bean 名称还是通过关联别名)。
从 6.1 版本开始,这需要存在 -parameters
Java 编译器标志。从 6.2 版本开始,当参数名称与 bean 名称匹配且没有类型、限定符或主要条件覆盖匹配时,容器会应用快速快捷解析以绕过完整的类型匹配算法。因此,建议您的参数名称与目标 bean 名称匹配。
作为按名称注入的替代方案,可以考虑使用 JSR-250 的 @Resource
注解,该注解在语义上被定义为通过其唯一名称识别特定目标组件,而声明的类型在匹配过程中是不相关的。@Autowired
的语义则有所不同:在通过类型选择候选 bean 之后,仅在这些按类型选择的候选中考虑指定的 String
限定符值(例如,将 account
限定符与标有相同限定符标签的 bean 匹配)。
对于那些本身定义为集合、Map
或数组类型的 bean,@Resource
是一个不错的解决方案,可以通过唯一名称引用特定的集合或数组 bean。也就是说,只要在 @Bean
返回类型签名或集合继承层次结构中保留元素类型信息,你也可以通过 Spring 的 @Autowired
类型匹配算法来匹配集合、Map
和数组类型。在这种情况下,你可以使用限定符值在相同类型的集合中进行选择,如前一段所述。
@Autowired
也考虑自引用进行注入(即,引用回当前被注入的 bean)。详情请参见自注入。
@Autowired
适用于字段、构造函数和多参数方法,允许通过参数级别的限定符注解进行缩小。相比之下,@Resource
仅支持字段和具有单个参数的 bean 属性 setter 方法。因此,如果您的注入目标是构造函数或多参数方法,您应该坚持使用限定符。
您可以创建自己的自定义限定符注解。要这样做,请定义一个注解,并在您的定义中提供 @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/>
标签的子元素,然后指定 type
和 value
以匹配您的自定义限定符注解。type
会与注解的全限定类名进行匹配。或者,作为一种便利,如果不存在名称冲突的风险,您可以使用简短的类名。以下示例演示了这两种方法:
<?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;
// ...
}
这一行添加了
@Offline
注解。
class MovieRecommender {
@Autowired
@Offline 1
private lateinit var offlineCatalog: MovieCatalog
// ...
}
这一行添加了
@Offline
注解。
现在 bean 定义只需要一个限定符 type
,如下例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> // <1>
<!-- inject any dependencies required by this bean -->
</bean>
此元素指定限定符。
您还可以定义自定义限定符注解,这些注解除了接受简单的 value
属性外,还可以接受命名属性。如果在要自动装配的字段或参数上指定了多个属性值,则一个 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
}
要自动装配的字段使用自定义限定符注解,并包括 genre
和 format
两个属性的值,如以下示例所示:
- 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/>
元素及其属性优先,但如果没有这样的限定符,自动装配机制会退回到 <meta/>
标签中提供的值,如以下示例中的最后两个 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 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>