基于注释的微调与限定符自动连接
@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 会与具有相同限定符值的构造函数参数关联。
对于回退匹配(fallback match),bean名称被视为默认的限定符值。因此,你可以使用id为main来定义bean,而无需使用嵌套的限定符元素,这样也能得到相同的匹配结果。然而,尽管你可以利用这种约定来按名称引用特定的bean,但@Autowired本质上是基于类型的注入(type-driven injection),并允许使用可选的语义限定符(semantic qualifiers)。这意味着,即使使用bean名称作为回退选项,限定符值在类型匹配的过程中始终具有“缩小范围”的作用(即它们只能从更具体的类型中筛选出符合条件的bean)。这些限定符值并不能在语义上表达对唯一bean ID的引用。合适的限定符值有main、EMEA或persistent等,它们表示的是与bean ID无关的特定组件的特性;在某些情况下(如前面示例中的匿名bean定义),bean ID可能是自动生成的。
限定符也适用于类型化的集合,如前所述——例如,适用于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进行匹配)。
对于那些本身就是集合(Collection)、映射(Map)或数组(Array)类型的bean,使用@Resource是一个很好的解决方案,可以通过唯一名称来引用特定的集合或数组bean。不过,只要在@Bean的返回类型签名或集合继承层次结构中保留了元素类型信息,你也可以通过Spring的@Autowired类型匹配算法来匹配集合、映射和数组类型。在这种情况下,你可以使用限定符值在同一类型的集合中进行选择,如前一段所介绍的那样。
@Autowired 也会考虑自我引用进行注入(即,引用当前正在被注入的 Bean)。详情请参见 自我注入。
@Autowired 可以应用于字段、构造函数和多参数方法,允许通过参数级别的限定符注解来进一步限定目标。相比之下,@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/>标签的子元素添加,然后指定type和value以匹配您的自定义限定符注解。类型会与注解的全限定类名进行匹配。或者,如果不存在名称冲突的风险,为了方便起见,您也可以使用简短的类名。以下示例展示了这两种方法:
<?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 是一个枚举(enum),定义如下:
- 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>