目录
1.9.3. 使用@Primary对基于注释的自动装配进行微调
1.9.6. 使用 CustomAutowireConfigurer
1.9.9. 使用@PostConstruct 和 @PreDestroy
1.9. 基于注解的容器配置
在配置Spring时,注解是否比XML更好? 基于注解的配置的引入提出了这样一个问题:这种方法是否比XML“更好”。简短的回答是“is depends”,长的回答是每种方法都有其优缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注解在其声明中提供了大量上下文,从而使配置更短、更简洁。然而,XML擅长于在不接触组件源代码或重新编译它们的情况下装配组件。一些开发人员更喜欢在源代码附近进行连接,而另一些人则认为带注解的类不再是pojo,而且,配置变得分散和难以控制。
无论选择如何,Spring都可以容纳两种样式,甚至可以将它们混合在一起。 值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注解,而无需接触目标组件的源代码,并且就工具而言,Spring Tool Suite支持所有配置样式。
|
基于注解的配置提供了XML设置的替代方法,该配置依赖字节码元数据来装配组件,而不是尖括号声明。通过使用相关类,方法或字段声明上的注解,开发人员无需使用XML来描述bean的装配,而是将配置移入组件类本身。如示例中所述:RequiredBeansProcessor,结合使用BeanPostProcessor和注解,是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注解强制执行必需属性的可能性。 Spring 2.5使遵循相同的通用方法来驱动Spring的依赖注入成为可能。本质上,@ Autowired注解提供的功能与自动装配协作器中所述的功能相同,但具有更细粒度的控制和更广泛的适用性。 Spring 2.5还添加了对JSR-250注解的支持,例如@PostConstruct和@PreDestroy。 Spring 3.0增加了对javax.inject包中包含的JSR-330(Java依赖注入)注解的支持,例如@Inject和@Named。有关这些注解的详细信息,请参见相关章节。
注解注入在XML注入之前执行。因此,XML配置将覆盖通过这两种方法连接的属性的注解。 |
与往常一样,您可以将它们注册为单独的bean定义,但也可以通过在基于XML的Spring配置中包含以下标记来隐式注册它们(请注意包括上下文名称空间):
<?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/>
</beans>
(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,PersistenceAnnotationBeanPostProcessor和前述的RequiredAnnotationBeanPostProcessor。)
注解注入在XML注入之前执行。因此,XML配置将覆盖通过这两种方法连接的属性的注解。 |
<context:annotation-config />仅在定义它的相同应用程序上下文中查找关于bean的注解。 这意味着,如果将<context:annotation-config />放入DispatcherServlet的WebApplicationContext中,则它将仅检查控制器中的@Autowired bean,而不检查服务。 有关更多信息,请参见DispatcherServlet。
1.9.1. @Required
@Required注解适用于bean属性设置器方法,如以下示例所示:
java 示例代码
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
kotlin示例代码
class SimpleMovieLister {
@Required
lateinit var movieFinder: MovieFinder
// ...
}
此注解表示必须在配置时通过bean定义中的显式属性值或通过自动装配来填充受影响的bean属性。 如果尚未填充受影响的bean属性,则容器将引发异常。 这允许急切和显式的失败,避免以后再出现NullPointerException实例等。 我们仍然建议您将断言放入bean类本身中(例如,放入init方法中)。 这样做会强制执行那些必需的引用和值,即使您在容器外部使用该类也是如此。
从Spring Framework 5.1开始,@ Required注解已正式弃用,转而使用构造器注入进行必需的设置(或InitializingBean.afterPropertiesSet()的自定义实现以及bean属性设置器方法)。 |
1.9.2. 使用 @Autowired
在本节中的示例中,可以使用JSR 330的@Inject注解代替Spring的@Autowired注解。 有关更多详细信息,请参见此处。 |
您可以将@Autowired注解应用于构造函数,如以下示例所示:
java 示例代码
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
kotlin示例代码
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao)
从Spring Framework 4.3开始,如果目标bean仅定义一个构造函数作为开始,则不再需要在此类构造函数上使用@Autowired注解。 但是,如果有几个构造函数可用,则必须至少使用@Autowired注解一个,以指示容器使用哪个构造函数。 |
您还可以将@Autowired注解应用于传统的setter方法,如以下示例所示:
java 示例代码
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
kotlin示例代码
class SimpleMovieLister {
@Autowired
lateinit var movieFinder: MovieFinder
// ...
}
您还可以将注解应用于具有任意名称和多个参数的方法,如以下示例所示:
java 示例代码
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
kotlin示例代码
class MovieRecommender {
private lateinit var movieCatalog: MovieCatalog
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Autowired
fun prepare(movieCatalog: MovieCatalog,
customerPreferenceDao: CustomerPreferenceDao) {
this.movieCatalog = movieCatalog
this.customerPreferenceDao = customerPreferenceDao
}
// ...
}
您还可以将@Autowired应用于字段,甚至可以将其与构造函数混合,如下例所示:
java 示例代码
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
kotlin示例代码
class MovieRecommender @Autowired constructor(
private val customerPreferenceDao: CustomerPreferenceDao) {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
确保目标组件(例如,MovieCatalog或CustomerPreferenceDao)由用于@Autowired注解的注入点的类型一致地声明。 否则,注入可能会由于运行时出现"no type match found"错误而失败。
对于通过类路径扫描找到的XML定义的bean或组件类,容器通常预先知道具体的类型。但是,对于@Bean factory方法,您需要确保声明的返回类型具有足够的表达能力。对于实现多个接口的组件或可能由其实现类型引用的组件,请考虑在工厂方法上声明最特定的返回类型(至少与引用bean的注入点所需的特定类型一样)。 |
您还可以通过将@Autowired注解添加到需要该类型数组的字段或方法中,指示Spring从ApplicationContext提供特定类型的所有bean,如下例所示:
java 示例代码
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
private lateinit var movieCatalogs: Array<MovieCatalog>
// ...
}
这同样适用于类型化集合,如下例所示:
java 示例代码
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Set<MovieCatalog>
// ...
}
如果您希望数组或列表中的项以特定顺序排序,则目标bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority注解。 否则,它们的顺序将遵循容器中相应目标bean定义的注册顺序。
您可以在目标类级别和@Bean方法上声明@Order注解,这可能适用于单个bean定义(如果使用同一bean类的多个定义)。 @Order值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的依赖关系。
注意,标准javax.annotation.Priority注解在@Bean级别不可用,因为它不能在方法上声明。它的语义可以通过@Order值和@Primary对每种类型的单个bean进行建模。 |
只要预期的密钥类型为String,即使是键入的Map
实例也可以自动装配。 映射值包含所有预期类型的bean,并且键包含相应的bean名称,如以下示例所示:
java 示例代码
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
lateinit var movieCatalogs: Map<String, MovieCatalog>
// ...
}
默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配将失败。 对于声明的数组,集合或映射,至少应有一个匹配元素。
默认行为是将带注解的方法和字段视为指示所需的依赖项。 您可以按照以下示例中所示的方式更改此行为,从而使框架能够通过将其标记为不需要来跳过不满意的注入点(即,通过将@Autowired中的required属性设置为false):
java 示例代码
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
kotlin示例代码
class SimpleMovieLister {
@Autowired(required = false)
var movieFinder: MovieFinder? = null
// ...
}
如果一个非必需的方法的依赖项(或者在多个参数的情况下,它的一个依赖项)不可用,则根本不会调用该方法。在这种情况下,不需要的字段根本不会被填充,而是保留其默认值。
注入的构造函数和工厂方法参数是一种特殊情况,因为@Autowired中的required
属性有一些不同的含义,这是因为Spring的构造函数解析算法可能处理多个构造函数。默认情况下,构造函数和工厂方法参数实际上是必需的,但是在单个构造函数场景中有一些特殊的规则,例如,如果没有匹配的bean可用,则多元素注入点(arrays, collections, maps)解析为空实例。这允许使用一个通用的实现模式,其中所有依赖项都可以在一个唯一的多参数构造函数中声明,例如,声明为一个没有@Autowired注解的公共构造函数。
任何给定bean类的构造函数都只能声明@Autowired,并将required属性设置为true,这表示在用作Spring bean时可以自动装配的构造函数。 此外,如果required属性设置为true,则只能使用@Autowired注释单个构造函数。 如果多个不需要的构造函数声明了注释,则它们将被视为自动装配的候选对象。 将选择通过匹配Spring容器中的bean可以满足的依赖关系数量最多的构造函数。 如果没有一个候选者满意,则将使用主/默认构造函数(如果存在)。 如果一个类仅声明一个单一的构造函数开始,即使没有注解,也将始终使用它。 带注解的构造函数不必是公共的。
建议在setter 方法上使用@Autowired的required属性,而不推荐使用已弃用的@Required注解。 将required属性设置为false表示该属性对于自动装配不是必需的,并且如果无法自动装配该属性将被忽略。 另一方面,@Required更为强大,因为它可以通过容器支持的任何方式强制设置属性,并且如果未定义任何值,则会引发相应的异常。
|
另外,您可以通过Java 8的java.util.Optional表示特定依赖项的非必需性质,如以下示例所示:
java 示例代码
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring Framework 5.0开始,您还可以使用@Nullable注解(在任何包中都可以使用任何形式,例如JSR-305中的javax.annotation.Nullable)或仅利用Kotlin内置的null安全支持:
java 示例代码
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
kotlin示例代码
class SimpleMovieLister {
@Autowired
var movieFinder: MovieFinder? = null
// ...
}
您也可以将@Autowired用于众所周知的可解决依赖项的接口:BeanFactory,ApplicationContext,Environment,ResourceLoader,ApplicationEventPublisher和MessageSource。 这些接口及其扩展接口(例如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,而无需进行特殊设置。 下面的示例自动装配ApplicationContext对象:
java 示例代码
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
lateinit var context: ApplicationContext
// ...
}
@Autowired,@Inject,@Value和@Resource注解由Spring BeanPostProcessor实现处理。 这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注释。 必须使用XML或Spring @Bean方法显式“连接”这些类型。 |
1.9.3. 使用@Primary对基于注释的自动装配进行微调
由于按类型自动装配可能会导致多个候选对象,因此通常有必要对选择过程进行更多控制。 一种实现方法是使用Spring的@Primary注释。 @Primary表示当多个bean可以自动连接到单值依赖项的候选对象时,应优先使用特定的bean。 如果候选中恰好存在一个主bean,它将成为自动装配的值。
参考以下将firstMovieCatalog定义为主MovieCatalog的配置:
java 示例代码
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
kotlin示例代码
@Configuration
class MovieConfiguration {
@Bean
@Primary
fun firstMovieCatalog(): MovieCatalog { ... }
@Bean
fun secondMovieCatalog(): MovieCatalog { ... }
// ...
}
使用前面的配置,下面的MovieRecommender将与firstMovieCatalog自动装配:
java 示例代码
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
private lateinit var movieCatalog: MovieCatalog
// ...
}
相应的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" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4. 使用限定符微调基于注解的自动装配
当可以确定一个主要候选者时,@ Primary是在几种情况下按类型使用自动装配的有效方法。 当您需要对选择过程进行更多控制时,可以使用Spring的@Qualifier注解。 您可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便为每个参数选择特定的bean。 在最简单的情况下,这可以是简单的描述性值,如以下示例所示:
java 示例代码
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
@Qualifier("main")
private lateinit var movieCatalog: MovieCatalog
// ...
}
您还可以在各个构造函数参数或方法参数上指定@Qualifier注解,如以下示例所示:
java 示例代码
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
kotlin示例代码
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与限定有相同值的构造函数参数装配。
对于后备匹配,bean名称被视为默认的限定符值。 因此,可以使用id为main而不是嵌套的qualifier元素定义bean,从而得到相同的匹配结果。 但是,尽管您可以使用此约定按名称引用特定的bean,但是@Autowired基本上是关于带有可选语义限定符的类型驱动的注入。 这意味着限定符值,即使具有bean名称回退,也总是在类型匹配集中具有狭窄的语义。 它们没有在语义上表示对唯一bean id的引用。 好的限定符值是main或EMEA或persistent,表示特定组件的特性,这些特性独立于bean id,在使用匿名bean定义(例如上例中的定义)的情况下,可以自动生成该特定组件的特性。
限定符也适用于集合类型,如前面所述(例如,适用于Set<MovieCatalog>)。 在这种情况下,根据声明的限定词,所有匹配的bean都作为一个集合注入。 这意味着限定词不必是唯一的。 相反,它们构成了过滤标准。 例如,您可以使用相同的限定符值“ action”定义多个MovieCatalog Bean,所有这些都注入到注有@Qualifier(“ action”)的Set<MovieCatalog>中。
在类型匹配的候选对象中,让限定符值针对目标Bean名称进行选择,在注入点不需要@Qualifier注解。 如果没有其他解析度指示符(例如限定词或主标记),则对于非唯一依赖性情况,Spring将注入点名称(即字段名称或参数名称)与目标Bean名称进行匹配,然后选择 同名候选bean(如果有)。 |
就是说,如果您打算按名称表示注解驱动的注入,则即使它能够在类型匹配的候选对象中按bean名称进行选择,也不要主要使用@Autowired。 而是使用JSR-250 @Resource批注,该批注的语义定义是通过其唯一名称标识特定目标组件,而声明的类型与匹配过程无关。 @Autowired具有不同的语义:按类型选择候选bean之后,仅在那些类型选择的候选中考虑指定的String限定符值(例如,将帐户限定符与标记有相同限定符标签的bean进行匹配)。
对于本身定义为collection,Map或array 类型的bean,@Resource是一个很好的解决方案,它通过唯一的名称引用特定的集合或数组bean。 也就是说,从4.3版本开始,您可以通过Spring的@Autowired类型匹配算法来匹配Map和数组类型,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中即可。 在这种情况下,您可以使用限定符值在同类型的集合中进行选择,如上一段所述。
从4.3开始,@ Autowired还考虑了自我引用以进行注入(即,引用回当前注入的Bean)。 请注意,自我注入是一个备选。 对其他组件的常规依赖始终优先。 从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其是绝不是主要的。 相反,它们总是以最低优先级结束。 实际上,您应该仅将自我引用用作最后的手段(例如,通过Bean的事务代理在同一实例上调用其他方法)。 考虑在这种情况下将受影响的方法分解为单独的委托bean。 或者,您可以使用@Resource,它可以通过其唯一名称获取返回到当前bean的代理。
尝试将@Bean方法的结果注入相同的配置类也实际上是一种自引用方案。 要么在实际需要的方法签名中延迟解析这些引用(与配置类中的自动装配字段相对),要么将受影响的@Bean方法声明为静态,将它们与包含的配置类实例及其生命周期脱钩。 否则,仅在备选阶段考虑此类Bean,而将其他配置类上的匹配Bean选作主要候选对象(如果可用)。 |
@Autowired适用于字段,构造函数和多参数方法,从而允许在参数级别缩小限定符注释的范围。 相反,只有具有单个参数的字段和bean属性setter 方法才支持@Resource。 因此,如果注入目标是构造函数或多参数方法,则应坚持使用限定符。
您可以创建自己的自定义限定符注释。 为此,请定义一个注解并在定义中提供@Qualifier注释,如以下示例所示:
java 示例代码
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
kotlin示例代码
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Genre(val value: String)
然后,您可以在自动装配的字段和参数上提供自定义限定符,如以下示例所示:
java 示例代码
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
kotlin示例代码
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形式提供限定符元数据。 具体来说,请参阅为带修饰符的限定符元数据提供。
在某些情况下,使用没有值的注解就足够了。 当注释具有更一般的用途并且可以应用于几种不同类型的依赖项时,这将很有用。 例如,您可以提供一个脱机目录,当没有Internet连接可用时,可以对其进行搜索。 首先,定义简单注解,如以下示例所示:
java 示例代码
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
kotlin示例代码
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class Offline
然后将注解添加到要自动装配的字段或属性,如以下示例所示:
java 示例代码
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
kotlin示例代码
class MovieRecommender {
@Autowired
@Offline
private lateinit var offlineCatalog: MovieCatalog
// ...
}
现在,bean定义仅需要限定符类型,如以下示例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
您还可以定义自定义限定符注角,以接受除简单value
属性之外或代替简单value
属性的命名属性。 如果随后在要自动装配的字段或参数上指定了多个属性值,则Bean定义必须与所有此类属性值匹配才能被视为自动装配候选。 例如,请参考以下注解定义:
java 示例代码
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
kotlin示例代码
@Target(AnnotationTarget.FIELD, AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.RUNTIME)
@Qualifier
annotation class MovieQualifier(val genre: String, val format: Format)
在这种情况下,Format是一个枚举,定义如下:
java 示例代码
public enum Format {
VHS, DVD, BLURAY
}
kotlin示例代码
enum class Format {
VHS, DVD, BLURAY
}
要自动装配的字段将用自定义限定符进行注解,并包括两个属性(genre和format)的值,如以下示例所示:
java 示例代码
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;
// ...
}
kotlin示例代码
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>
1.9.5. 将泛型用作自动装配限定符
除了@Qualifier注解之外,您还可以使用Java泛型类型作为限定的隐式形式。例如,假设您具有以下配置:
java 示例代码
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
kotlin示例代码
@Configuration
class MyConfiguration {
@Bean
fun stringStore() = StringStore()
@Bean
fun integerStore() = IntegerStore()
}
假设前面的bean实现了通用接口(即Store<String>和Store<Integer>),则可以@Autowire Store接口,并将通用接口用作限定符,如以下示例所示:
java 示例代码
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
kotlin示例代码
@Autowired
private lateinit var s1: Store<String> // <String> qualifier, injects the stringStore bean
@Autowired
private lateinit var s2: Store<Integer> // <Integer> qualifier, injects the integerStore bean
通用限定符也适用于自动装配List、Map实例和Array时。以下示例自动连接泛型List:
java 示例代码
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private List<Store<Integer>> s;
kotlin示例代码
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
private lateinit var s: List<Store<Integer>>
1.9.6. 使用 CustomAutowireConfigurer
CustomAutowireConfigurer是一个BeanFactoryPostProcessor,允许您注册自己的自定义限定符注解类型,即使它们没有用Spring的@qualifier注解进行注解。下面的示例演示如何使用CustomAutowireConfigurer:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver通过以下方式确定自动装配候选:
-
每个bean定义的autowire-candidate值
-
<beans />元素上可用的任何默认autowire-candidates模式
-
@Qualifier
注解
以及在CustomAutowireConfigurer中注册的所有自定义注解
的存在
当多个bean符合自动装配候选条件时,“primary”的确定如下:如果候选中恰好有一个bean定义的primary属性设置为true,则将其选中。
1.9.7. 使用 @Resource 注入
Spring还支持对字段或bean属性setter方法使用JSR-250@Resource注解(javax.annotation.Resource)进行注入。这是Java EE中的常见模式:例如,在JSF托管bean和JAX-WS端点中。Spring也支持Spring管理对象的这种模式。
@Resource具有名称属性。 默认情况下,Spring将该值解释为要注入的Bean名称。 换句话说,它遵循名称语义,如以下示例所示:
java 示例代码
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
kotlin示例代码
class SimpleMovieLister {
@Resource(name="myMovieFinder")
private lateinit var movieFinder:MovieFinder
}
如果未明确指定名称,则默认名称是从字段名称或setter方法派生的。 如果是字段,则采用字段名称。 在使用setter方法的情况下,它采用bean属性名称。 以下示例将把名为movieFinder的bean注入其setter方法中:
java 示例代码
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
kotlin示例代码
class SimpleMovieLister {
@Resource
private lateinit var movieFinder: MovieFinder
}
注解提供的名称由CommonAnnotationBeanPostProcessor知道的ApplicationContext解析为Bean名称。 如果您明确配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析名称。 但是,我们建议您依赖默认行为,并使用Spring的JNDI查找功能来保留间接级别。 |
在@Resource用法(不指定显式名称)的特殊情况下,类似于@Autowired,@Resource查找主类型匹配而不是特定的命名Bean,并解析众所周知的可解决依赖关系:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher和 MessageSource接口。
因此,在以下示例中,customerPreferenceDao字段首先查找名为“ customerPreferenceDao”的bean,然后回退到类型为CustomerPreferenceDao的主类型匹配项:
java 示例代码
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
kotlin示例代码
class MovieRecommender {
@Resource
private lateinit var customerPreferenceDao: CustomerPreferenceDao
@Resource
private lateinit var context: ApplicationContext
// ...
}
context字段是基于已知的可解决依赖项类型ApplicationContext注入的。
1.9.8. 使用 @Value
@Value通常用于注入外部属性:
java 示例代码
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
kotlin示例代码
@Component
class MovieRecommender(@Value("\${catalog.name}") private val catalog: String)
使用以下配置:
java 示例代码
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
kotlin示例代码
@Configuration
@PropertySource("classpath:application.properties")
class AppConfig
以及以下application.properties文件:
catalog.name=MovieCatalog
在这种情况下,catalog
参数和字段将等于MovieCatalog值。
Spring提供了一个默认的宽松内嵌值解析器。 它将尝试解析属性值,如果无法解析,则将属性名称(例如$ {catalog.name})作为值注入。 如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurer bean,如以下示例所示:
java 示例代码
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
kotlin示例代码
@Configuration
class AppConfig {
@Bean
fun propertyPlaceholderConfigurer() = PropertySourcesPlaceholderConfigurer()
}
使用JavaConfig配置PropertySourcesPlaceholderConfigurer时,@ Bean方法必须是静态的。 |
如果无法解析任何${}占位符,则使用上述配置可确保Spring初始化失败。 也可以使用setPlaceholderPrefix,setPlaceholderSuffix或setValueSeparator之类的方法来自定义占位符。
Spring Boot默认配置一个PropertySourcesPlaceholderConfigurer bean,它将从application.properties和application.yml文件获取属性。 |
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为Integer或int)。 多个逗号分隔的值可以自动转换为String数组,而无需付出额外的努力。
可以提供如下默认值:
java 示例代码
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
kotlin示例代码
@Component
class MovieRecommender(@Value("\${catalog.name:defaultCatalog}") private val catalog: String)
Spring BeanPostProcessor在后台使用ConversionService处理将@Value中的String值转换为目标类型的过程。 如果要为自己的自定义类型提供转换支持,则可以提供自己的ConversionService bean实例,如以下示例所示:
java 示例代码
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
kotlin示例代码
@Configuration
class AppConfig {
@Bean
fun conversionService(): ConversionService {
return DefaultFormattingConversionService().apply {
addConverter(MyCustomConverter())
}
}
}
当@Value包含SpEL表达式时,该值将在运行时动态计算,如以下示例所示:
java 示例代码
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
kotlin示例代码
@Component
class MovieRecommender(
@Value("#{systemProperties['user.catalog'] + 'Catalog' }") private val catalog: String)
SpEL还支持使用更复杂的数据结构:
java 示例代码
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
kotlin示例代码
@Component
class MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") private val countOfMoviesPerCatalog: Map<String, Int>)
1.9.9. 使用@PostConstruct
和 @PreDestroy
CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,还可以识别JSR-250生命周期注解:javax.annotation.PostConstruct和javax.annotation.PreDestroy。 在Spring 2.5中引入的对这些注解的支持提供了初始化回调和销毁回调中描述的生命周期回调机制的替代方法。 假设CommonAnnotationBeanPostProcessor已在Spring ApplicationContext中注册,则在生命周期中与相应的Spring生命周期接口方法或显式声明的回调方法在同一点调用带有这些注解之一的方法。 在以下示例中,缓存在初始化时预先填充,并在销毁时清除:
java 示例代码
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
kotlin示例代码
class CachingMovieLister {
@PostConstruct
fun populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
fun clearMovieCache() {
// clears the movie cache upon destruction...
}
}
有关组合各种生命周期机制的效果的详细信息,请参见组合生命周期机制。
与@Resource一样,@ PostConstruct和@PreDestroy注解类型是JDK 6到8的标准Java库的一部分。但是,整个javax.annotation包与JDK 9中的核心Java模块分开,并最终在JDK 11中删除了。 如果需要,现在需要通过Maven Central获取javax.annotation-api工件,只需像其他任何库一样将其添加到应用程序的类路径中即可。 |