Spring IOC之基于注解的容器配置
Spring配置中注解比XML更好吗?基于注解的配置的介绍提出的问题是否这种途径比XML更好。简单来说就是视情况而定。
长一点的答案是每一种方法都有自己的长处也不足,而且这个通常取决于开发者决定哪一种策略更适合他们。和他们被定
义的方式有关,注解在他们的定义中提供了大量的上下文信息,这样会提供更少更准确的配置。但是XML的优势是他不需要
访问他们的源代码也不需要重新编译他们在设置这些组件信息时。一些开发者更有去和源码相关操作而其他人质疑注解标识
的类不再是POJOs了,还有就是注解变成了分散管理了而且很难受控制。
不管事什么选择,Spring都可以支持这两种风格而且甚至是可以把它们混杂咋一起。可以指出的是它是通过JavaConfig选择,
Spring允许注解被使用通过一种非侵入式方法,而不用访问目标组建的源码,这些配置风格也是被SpringSource Tool Suite支持的。
作为XML之外的其他选择,基于注解的配置是依赖字节码来装配组件的,而不是使用尖括号来生命的。不适用XML去描述一个bean装配,
开发者通过使用在相关类中、方法、域中使用注解来讲配置信息注入到相关类中。正如原来提到过的叫着“例子:The RequiredAnnotationBeanPostProcessor”的
部分,和注解一块使用使用一个BeanPostProcessor时常用的方式来扩展Spring IOC容器。例如,在Spring2.0中使用@Required注解来强迫被要求的属性。
Spring 2.5通过使用类似的方式来驱动Spring DI 成为了可能。@Autowird注解提供了和 “Autowiring collaborators”相似的功能但是用了
更细粒度的控制盒更广的应用。Spring 2.5也同时增加了JSR-250中像@PostConstruct和@PreDestroy.这样的注解。Spring 3.0
早呢更加了支持JSR-330注解,包括the javax.inject包中的 @Injectand 和@Named。
注意:注解配置生效在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
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/>
</beans>
(这样隐含的注解的post-processor包括了AutowiredAnnotationBeanPostProcessor,CommonAnnotationBeanPostProcessor,
PersistenceAnnotationBeanPostProcessor, RequiredAnnotationBeanPostProcessor)。
注意:<context:annotation-config/>只会查看在相同的应用上下文中被定义的Bean中的注解。这就意味着,如果你将
WebApplicationContext中的<context:annotationconfig/>放置到DispatcherServlet中,它只会查看在你的控制器中的
@Autowird beans而不是你的services。
@Required
@Required注解用在bean属性的setter方法上,正如下面的例子:
public classSimpleMovieLister {
privateMovieFinder movieFinder;
@Required
public voidsetMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
这个注解表明受影响的bean属性必须在配置的时候被填入,可以通过在bean中定义的明确的属性值或者是通过自动装配。如果
受作用的bean没有被填入那么就会抛出异常;要避免NullPointerExceptions异常。建议你将断言添加在bean的类中,例如,添加到
初始化方法中。当你在容器的外表使用这些类的时候可以强迫你必要的引用或者值。
@Autowired
正如期望的一样,你可以使用@Autowired注解在传统的setter方法中:
public classSimpleMovieLister {
privateMovieFinder movieFinder;
@Autowired
public voidsetMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
注意:JSR 330的 @Inject 注解能够在Spring的@Autowired注解的位置处;
你可以将这个注解使用在任意方法名字和多个参数的方法上:
public classMovieRecommender {
privateMovieCatalog movieCatalog;
privateCustomerPreferenceDao customerPreferenceDao;
@Autowired
public voidprepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你也可以使用@Autowird注解在构造器或者域上面:
public classMovieRecommender {
@Autowired
privateMovieCatalog movieCatalog;
privateCustomerPreferenceDao customerPreferenceDao;
@Autowired
publicMovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
通过添加注解到一个需要数组类型的方法或者域上面的方式从ApplicationContext中提供指定类型的bean:
public classMovieRecommender {
@Autowired
privateMovieCatalog[] movieCatalogs;
// ...
}
相似的可以用在集合中:
public classMovieRecommender {
privateSet<MovieCatalog> movieCatalogs;
@Autowired
public voidsetMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
建议:如果你想在数组或者列表中的元素被按照指定的顺序来排序的话,你可以实现 org.springframework.core.Ordered
或者使用@Order注解。
只要被期望的key类型是String,那么即使是指定类型的Maps也可以被自动装配。Map的值就会包含所有bean的邢磊,这个key
就会包含相关的bean 的名字:
public classMovieRecommender {
privateMap<String, MovieCatalog> movieCatalogs;
@Autowired
public voidsetMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,任何时候0 待用的bean可以使用的时候,自动注入就会失败;这个默认的行为或在表明需要的依赖的注解方法、构造器、和域。
这种方式可以被下面的设置更改:
public classSimpleMovieLister {
privateMovieFinder movieFinder;
@Autowired(required=false)
public voidsetMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
注意:每一个类中只有一个注解的构造器能够标示为required,但是多个没有的non-reuired构造器能够使用注解。在这个例子中,在待用的每一个都会被考虑,
Spring使用依赖和使用的构造器,也就是说拥有最多数量的参数的构造器。@Autowired需要的属性被建议使用了@Required注解。required属性标示一个属性在自
动装配的目的中是不需要的,如果他不能够被自动注入属性就会被忽略。在另一方面,@Required在强迫在容器中被任何支持的方式中设置属性方面是很很强大的。
如果没有值被注入,一个相关的异常就会产生。
你可以在那些众所周知能够被解析的接口中使用@Autowired:BeanFactory, ApplicationContext, Environment, ResourceLoader,
ApplicationEventPublisher, 和 MessageSource.。他们的接口或者是他们扩展的接口,比如:ConfigurableApplicationContext 和 ResourcePatternResolver能够
被自动解析而不需要特别其他步骤:
public classMovieRecommender {
@Autowired
privateApplicationContext context;
publicMovieRecommender() {
}
// ...
}
注意:@Autowired, @Inject, @Resource, 和 @Value注解是被一个Spring 的 BeanPostProcessor来操作的。反过来讲,这意味着你不能将这些注解应用在你自己的
BeanPostProcessoror或者BeanFactoryPostProcessor类型。这些依赖必须被通过XML或者一个Spring @Bean来显示的装配。
使用限定词来微调基于注解的自动装配
以为按类型的自动装配可能导致多个可用的Bean,在选择的过程中有很多歌控制是和很有必须要的。完成这个方式之一就是使用Spring @qualifier注解。你可以以将限定词与
指定的参数联系在一起,从而缩小匹配的类型集合以便对于每一个参数只有一个指定的Bean被选中。在最简单的 例子中,可以像下面这样:
public classMovieRecommender {
@Autowired
@Qualifier("main")
privateMovieCatalog movieCatalog;
// ...
}
@Qualifier注解可以再单独的构造器参数或者方法参数总明确:
public classMovieRecommender {
privateMovieCatalog movieCatalog;
privateCustomerPreferenceDao customerPreferenceDao;
@Autowired
public voidprepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
相关的Bean定义下面,拥有限定值 “main”的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 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名字呗认为是默认的限定词的值。这样,你可以用一个id“main”而
不是一个嵌套的限定元素来定义这Bean,这样也会导致相同的匹配结果。但是,尽管你可以通过名字使用会话来引用指明的Bean,
@Auotowird是在可选的限定中通过类型了匹配。这意味找限定的值,即使是使用一个Bean 名字,也会在一个类型匹配的集合中有
更窄的语义选择。他们不会将一个引用指向唯一的Bean id.好的限定词是“main”、或者“EMAEA”或者“persisent”,这表明
指定的组件 的特征是独立于Bean的id的,这个可能在一个匿名的Bean定义中自动生成。
限定符可以使用在上面的讨论的类型集合中,例如,使用在Set
Bean就会作为一个集合被注入。这个表明限定符不是必须要唯一的,他们只需要简单的匹配过滤的关键字。例如,你可以定义多个MovieCaglog
Bean 使用相同的限定词 “action”,所有用注解表示的@Qualifier("action")都会诶注如到Set
建议:如果你打算通过名字来表示注解驱动的注入,不要主要的使用@Autowird。它还可以通过引用一个Bean通过@Qualifier值来满足于鏊求。
作为取代的话,你可以使用JSR-250中的@Resource注解,这个可以再语法层面定义去指明一个目标组件通过唯一的名字和不相干的声明的匹配类型。
作为这个语义上的不同的,Bean 自己被定义成为一个集合或者Map类型,,它不能通过@Autowird注入。因为类型匹配并不能合适的适用在他们上面。
使用@Resource对于这样的Bean,通过唯一的名字来应用指明的集合或者Map Bean。@Autowird适用于域、构造器和多参数的方法,在参数层来通过限制符
注解来减少选定范围。形成对比的是,@Resource只是对于域或者Bean属性的只哟一个参数的setter方法所支持。因此,
如果你的注入对象时一个构造器或者多参数的方法使用限定符。
你可以创建自己的自定义限定符注解。简单的定义一个注解并且在你的定义中提供一个Qualifier注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interfaceGenre {
String value();
}
你可以在自动装配的域和参数中使用自定义限定符:
public classMovieRecommender {
@Autowired
@Genre("Action")
privateMovieCatalog actionCatalog;
privateMovieCatalog comedyCatalog;
@Autowired
public voidsetComedyCatalog(@Genre("Comedy")MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
下一步,你可选的Bean定义中提供添加这些信息。你可以添加
type和value。类型不能使 fully-qualified的类名的注解匹配。或者说,为了方便起见,如果没有冲突的名字的风险存在的话,你可以使用短类名字。这两种方式如下:
<?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>
在一些用例中,使用一个没有值的注解是足够的。当注解用在一个泛型 的目标上而且被引用在集合不同类型的依赖中会是很有效的。
例如,你可以提供一个 offline 策略在没有互联网集合可用的时候就会被搜索到。首先定义简单的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interfaceOffline {
}
添加注解到被自动装配的域或者属性上:
public classMovieRecommender {
@Autowired
@Offline
privateMovieCatalog offlineCatalog;
// ...
}
现在Bean的定义只需要一个限定符 type:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
你可以定义接受额外需要命名的属性或者简单的属性值的自定义限定符注解。如果多个属性值在一个需啊哟自动装配的域或者参数上指明的时候。
一个Bean定义在必须匹配所有的这样属性值。例如,看下下面的注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interfaceMovieQualifier {
String genre();
Format format();
}
在这个例子中,Format是枚举类型的:
publicenum Format {
VHS, DVD, BLURAY
}
将被自动装配的域被自动以的限定符注解标示,包括了两个属性的值:genre和 format:
public classMovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
privateMovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
privateMovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
privateMovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
privateMovieCatalog comedyBluRayCatalog;
// ...
}
最后,Bean定义应该包括匹配限定符的值。这个例子展示了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>
使用泛型作为自动装配限定符
除了@Qualifier注解,也可以使用Java泛型作为一个限定符的隐式形式。例如,假设你有下面的配置:
@Configuration
public classMyConfiguration {
@Bean
publicStringStore stringStore() {
return newStringStore();
}
@Bean
publicIntegerStore integerStore() {
return newIntegerStore();
}
}
假设上面的Bean 实现了一个泛型接口,比如Store
@Autowired
privateStore<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
privateStore<Integer> s2; // <Integer> qualifier, injects the integerStore bean
当自动装配Lists、Maps、Arrays时泛型限定符也可以使用:
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans will not appear in this list
@Autowired
privateList<Store<Integer>> s
CustomAutowireConfigurer
CustomAutowireConfigurer是一个BeanFactoryPostProcessor,它可以让你注册自己的自定义限定符类型尽管他们没有被Spring的@Qualifier注解所注解:
<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值
- 在
元素中任何 default-autowire-candidates 模式可用 - @Qualifier注解和使用CustomAutowireConfigurer注册的任何自定义注解
当多个Bean 限定为自动装配的候选者,决定首选的是通过下面:如果很明显一个在候选者中的Bean的定义有一个primary属性设置为true,它将会被选中。
@Resource
Spring 还支持在域或者Bean属性setter方法上面的 JSR250注解。这个在JavaEE 5和6 是常见的模式,例如在JSF1.2管理的Bean中 或者是JAX-WS2.0断电。
Spring支持使用这种凡事对于S平日凝固干礼的对象。
@Resource 有一个name 属性,默认情况下 Spring解析那个值作为Bean名字来注入。缓慢句话说,它遵循按名字的语法,正如下面表示的一样:
public classSimpleMovieLister {
privateMovieFinder movieFinder;
@Resource(name="myMovieFinder")
public voidsetMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有名字显示的指明,默认的名字就会从属性的名字或者setter方法中获取。对于域而言,你采用给一个域的名字;对于setter方法而言,
它使用一个Bean属性的名字。所以下面的例子将会是Bean有一个moveFinder的名字注入到setter方法中:
public classSimpleMovieLister {
privateMovieFinder movieFinder;
@Resource
public voidsetMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注意:通过注解提供的名字ApplicationContext通知CommonAnnotationBeanPostProcessor解析成一个bean的名字的。
如果你配置Spring的 SimpleJndiBeanFactory,那么这个名字能够给通过JDNI解析。但是,强烈建议你依赖默认的选择
并且这是简单使用Spring的 JNDI查询的能力去间接的保存层次。
在特有的例子的 @Resource使用干么有显示的名字支持明,将会和 @Autowired类似,@Resource找到第一个匹配的类型而不是指明名字的bean并且解析知道的可解析的依赖:
BeanFactory, ApplicationContext, ResourceLoader,ApplicationEventPublisher, 和MessageSource接口。
所以在下面的例子中,customerPreferenceDao域首先找一个名字为CustomerPreferenceDao的bean,然后回溯到第一个类型匹配CustomerPreferenceDao.
context域被注入是基于一直的可解析依赖类型ApplicationContext。
public classMovieRecommender {
@Resource
privateCustomerPreferenceDao customerPreferenceDao;
@Resource
privateApplicationContext context;
publicMovieRecommender() {
}
// ...
}
@PostConstruct 和 @PreDestroy
CommonAnnotationBeanPostProcessor对于rocessor不但可以是被 @Resource注解还可以识别 JSR-250的 生命周期 注解。在Spring 2.5中引入的,对于这些注解的支持
提供了起其他方式去表示初始化会带哦方法和销毁时毁掉。只要CommonAnnotationBeanPostProcessor在Spring 的ApplicationContext内部被注入,含有这么多注解中一个
的方法在同时在生命周期中作为相应的Spring 生命周期接口方法或者显示的声明的毁掉方法。在下面的例子中,缓存将会被提供使用而在销毁中清除:
public classCachingMovieLister {
@PostConstruct
public voidpopulateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public voidclearMovieCache() {
// clears the movie cache upon destruction...
}
}