本文针对自动装载的一些注解进行描述。
基于注解的容器配置
@Required
注解
@Required
注解需要应用到Bean的属性的setter方法上面,如下面的例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
当Bean的属性配置了这个注解时,该Bean的属性必须在配置阶段就拥有明确的值,通过精确地Bean定义,或者通过自动装载。如果Bean的属性没有配置,容器会抛出异常。这一机制可以避免之后出现的空指针异常问题。当然,仍然推荐在Bean的类中使用断言,比如,在初始化方法中。这么做可以确保请求的引用或者值在容器外使用时是正确的。
@Autowired
JSR 330 的
@Inject
注解在下面的例子中可以用来取代Spring的@Autowired
注解。
开发者可以在构造函数上面使用@Autowired
注解:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
在Spring 4.3的版本中,
@Autowired
在构造函数上面的注解就已经不需要了,如果Bean定义了构造函数,会自动扫描直接DI的。如果配置了多个构造函数,则需要配置@Autowired
来告诉容器使用哪一个来进行依赖注入。
当然,@Autowired
可以注入到传统的setter方法上面:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
开发者也可以将这个注解运用到任何名字任何参数的方法上面:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
开发者也可以将@Autowired
用到域上面,甚至和构造函数混用:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
当然,也可以根据类型,将所有的ApplicationContext
中的类型的Bean进行依赖注入,可以通过注解将多个Bean注入到Array
类型中。
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
也可以注入到集合类型之中:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
开发者所定义的Bean可以通过实现
org.springframework.core.Ordered
接口或者通过使用@Order
或者标准的@Priority
注解来确定注入到array或者list中的顺序。
Map类型也可以注入进去,只要key的类型为String
就可以注入进去。Map的值可以是任何一种容器中的Bean的类型,key当然就是对应Bean的名字。
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认的话,自动装载会在没有候选Bean可用时失败,默认的行为就是将带有注解的方法,构造函数,以及实例变量作为必须的依赖。当然,这个默认的行为可以被改为如下代码:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required=false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
每个类仅有一个注解构造函数可以被标记为必须的,但是非必须的够早函数可以注解多个。在存在多个非必须注解(
required=false
)的情况下,Spring会选择一个最贪婪的构造函数(满足最多的依赖的)。
@Autowired
注解要优于@Required
注解。required
属性来表明其属性是否为必须的,而当依赖不能被注入的话,就会被忽略掉。而@Required
朱姐,是比@Autowired
要强的,它会要求依赖必须注入。如果没有对应的依赖进行注入,就会抛出异常。
开发者可以使用@Autowired
来自动装载一些依赖:BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
,ApplicationEventPublisher
以及MessageSource
等。这些接口以及其扩展接口,比如ConfigurableApplicationContext
或者ResourcePatternResolver
都可以解析,不用特殊配置。
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
,@Inject
,@Resource
以及@Value
等注解都是通过Spring的BeanPostProcessor
的实现来实现自动装载的,也就意味着开发者不能够通过自己的BeanPostProcessor
或者BeanFactoryPostProcessor
来应用这些注解。开发者自己的类型必须通过明确的XML或者Spring@Bean
方法来定义。
通过@Primary
来自动装载
由于通过类型来装载可能导致多个候选者,通常很有必要来控制选择依赖的过程,一种方式就是通过Spring的@Primary
注解。@Primary
注解表明当一个Bean需要进行依赖注入的时候,如果有多个候选者可能注入到单值的依赖之中,那么该Bean拥有优先权。如果只有一个primary的Bean的话,那么这个Bean将成为自动装载的依赖。
参考如下代码:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
上面的代码中,firstMovieCatalog
作为主要的MovieCatalog
。
当使用了上面的配置后,下面的Bean在自动装载的时候,会装载firstMovieCatalog
。
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
如果使用XML来进行定义的话,配置如下:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://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>
通过限定符的自动装载
@Primary
在相同类型的几个实例之间选择选择依赖的时候,是一种很高效的方式。但是如果想要更多的控制自动装载的过程,@Qualifier
注解就更为有用了。开发者可以通过将趣分期的值和参数来关联起来,以缩小类型匹配的范围,这样就能够自动装配到特值得Bean了。代码如下:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier
注解可以指定到构造函数的参数,或者是方法上面:
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;
}
// ...
}
而依赖的Bean的在XMl的定义,配置如下:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog">
<qualifier value="main"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/>
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
在匹配中,Bean的名字就相当于默认的限定符的值。这样,开发者可以定义一个Bean的id为main而不使用限定符元素,也会有一样的匹配结果的。然而,尽管开发者可以通过这个便利来通过名字引用指定的Bean,@Autowired
在根本上仍然是通过类型来进行依赖注入的,只不过有着可选的限定符。这也意味着限定符的值,甚至Bean的名字,总是能缩小值匹配的集合,但是并不能明确的如Bean的id一样指向一个唯一的Bean。好的限定符的值比如main
或者EMEA
或者persistent
,所表示的是特定的组件,跟Bean的id是相互独立的。Bean的id,在前面匿名Bean中是会自动生成的。
限定符也能够应用到集合类型的,如前面的例子中,Set<MovieCatalog>
。在这种情况下,所有根据声明的趣分期的匹配的Bean都会被注入到集合之中。这也说明趣分期并不是唯一的,限定符更类似一种过滤标准。比如:开发者可以定义多个MovieCatalog
Bean而且配置其相同的限定符的值,比如说action
,那么所有含有@Qualifier("action")
的Bean都会注入到Set<MovieCatalog>
之中。
如果开发者希望通过名字来进行依赖注入,甚至仅仅使用
@Qualifier
的值进行注入,那么可以不用@Autowired
而是使用JSR-250@Resource
注解,这个注解是通过名字来进行依赖注入的,而与类型无关。
对于本身定义为集合类型或者数组类型的Bean来说,@Resource
是个很好的解决方案,可以通过名字来引用特定的集合或者数组元素。当然,在Spring4.3的版本中,集合类型,map以及数组类型也能通过Spring的@Autowired
的类型匹配算法匹配到了。
@Autowired
可以用在实例变量,构造函数,或者多参数的方法上,允许通过限定符注解来缩小匹配范围。相比之下,@Resource
仅仅在实例变量和Setter方法的情况下支持依赖注入。因此,如果需要在多参数的方法或者构造函数上约束注入的依赖,需要使用限定符而不要用@Resource
开发者也可以定义自己的限定符注解,代码如下:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
之后就可以将自定义的限定符应用到自动装载的参数上面了,如下:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
下一步,需要给候选的Bean提供限定符信息。开发者可以通过在<bean/>
的子标签<qualifier/>
标签中来指定type
的值来匹配自定义的限定符注解。匹配类型注解是完整的类名的,需要和自定义的类名一致。或者,如果没有命名冲突的话,可以仅仅使用类名。参考XML代码如下:
<?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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://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>
在很多情况下,仅仅使用注解就足够了。而且这一点在注解服务更为泛化的情况下更为有用。比如说,开发者需要提供一个线下的目录的话,可以参考如下的定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
之后的使用不要指定值了,自动装载配置如下:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
XML中的配置也较为简单,如下:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
当然,开发者也可以在一个value
属性不足的情况下定义更多的限定符的参数来限定Bean的自动装载。如果在自定义的限定符注解中定义了多个限定的参数,那么Bean必须符合全部的参数才能被视为自动装载的候选者。参考如下的例子:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
其中Format是一个枚举类型。
public enum Format {
VHS, DVD, BLURAY
}
在前面自定义的限定符中,则必须满足两个属性的匹配才能够进行依赖注入:
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;
// ...
}
XML中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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://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>
可以看出,限定符的定义是类似key-value的形式存在于<qualifier/>
的子标签中的。除此之外,如果限定符有效,则优先选择匹配的Bean作为依赖进行注入,如果没有,自动装载的机制会在不存在限定符的时候自动退回到<meat/>
,如上述代码的最后两个Bean。
使用泛型作为自动装载的限定符
关于@Qualifier
注解,也可以使用Java泛型来作为限定符。比如说,有如下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设上面提到的Bean实现了一个泛型接口比如说:Store<String>
和Store<Integer>
,开发者可以使用@Autowire
来自动装载,而泛型的类型则作为限定符,参考如下代码:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
泛型的限定同样可以用于List,Map或者Array上面,如下:
// 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;
CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
,这个后置处理器可以注册开发者自己的限定符注解,让开发者的注解不依赖于Spring限定符注解。
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver
通过以下的几种方式来决定自动装载的候选Bean:
- Bean定义中的
autowire-candidate
的值 - 任何
<beans/>
标签中定义的default-autowire-candidates
的值 @Qualifier
注解和任何在CustomAutowireConfigurer
中定义的自定义的限定符注解
当多个Bean限定为自动装载的候选时, 前文中提到的primary
属性是优先考虑的。
@Resource
注解
Spring支持JSR-250标准中的@Resource
注解来注入实例变量或者setter方法。这也是Java EE 5和6之中的常见模式,举例来说,在JSF 1.2中管理的Bean或者是JAX-WS 2.0 endpoint等。Spring也支持这种模式来注入Spring管理的对象。
@Resource
需要一个名字的属性,而默认的情况下,Spring会将Bean的名字注入。换言之,@Resource
的自动装载是基于名字语义的,就如同如下的例子一样:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有明确指定名字的话,默认的名字就是实例变量的变量名,或者Setter方法中解析出来的名字。以实例变量为例,就取变量的名字作为默认的名字,如果是Setter的方法的话,会取Bean属性的名字。所以,如下的例子会注入Bean的名字的movieFinder
的Bean实例。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注解解析的名字是通过
ApplicationContext
来解析的。如果开发者配置了明确的SpringSimpleJndiBeanFactory
的话,这些名字可以通过JNDI来解析。然而,Spring团队仍然建议开发者依赖其默认的行为并简单实用Spring的JNDI查找能力。
在使用@Resource
而没有明确指定名字的情况,它比较类似@Autowired
注解,@Resource
会优先查找类型匹配而不是名字匹配,也能够解析一些常见的依赖:BeanFactory
,ApplicationContext
,ResourceLoader
,ApplicationEventPublisher
以及MessageSource
等接口。
在如下的例子中,customerPreferenceDao
实例变量会优先查找名字为customerPreferenceDao
的Bean来进行依赖注入,然后在进行类型匹配CustomerPreferenceDao
。context
实例变量作为常见的实例会自动通过ApplicationContext
注入进去。
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@PostConstruct
和PreDestroy
注解
CommonAnnotationBeanPostProcessor
不仅仅识别@Resource
注解,也识别JSR-250标准中的生命周期注解。在 Spring核心技术IoC容器(六)中针对回调函数的描述了注解外其他的方法实现回调。CommonAnnotationBeanPostProcessor
是注册在Spring的ApplicationContext
之中的,它提供了一些方法来关联Spring的声明周期来进行回调,在下面的例子中,缓存会在构造后和销毁前进行回调。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
关于Spring的生命周期的机制在前文 《Spring核心技术IoC容器(六)》之中有更详细的描述。