基于注解的配置比XML更好吗?这得看情况,XML解耦了配置和原代码,而注解则精简了配置。
注解的注入会在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/> </beans>
以上方式注册的post-processors(参考深入理解Spring4框架(七)——容器扩展点)包含AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor和RequiredAnnotationBeanPostProcessor。
<context:annotation-config/>仅查找同一个应用上下文中所定义的Bean。也就是说,如果将<context:annotation-config/>放到一个DispatcherServlet对应的WebApplicationContext中,它仅会检测控制器中加了@Autowired注解的类。
1. @Required
@Required注解应用于Bean属性的setter方法,如下:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
这个表明Bean的属性值必须在配置中指定,可以通过显示的在Bean定义中指定,也可以使用自动注入。若没有为这个属性指定值,那么容器会抛出一个异常。
2. @Autowired
正如所料,可以在普通setter方法上使用@Autowired注解。
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;
}
// ...
}
可以将注解应用到构造器和字段上:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
也可以把指定类型的所有Bean提供给一个数组类型的变量:
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,自动注入就会失败。若要改变默认的策略,可以如下修改注解:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required=false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
3. 使用@Primary调整基于注解的自动注入
因为根据类型的自动注入可能会出现多个符合条件的Bean,那么很有必要在选择的流程上拥有更多的控制权。一种解决方式就是使用Spring的@Primary注解。@Primary表明这个Bean在被注入时拥有更高的优先级,也就是说,在自动注入时若找到多个符合条件的Bean,那么被@Primary注解的Bean将会最终被注入。
看如下示例,将firstMovieCatalog定义为主要的MovieCatalog:
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
那么,下面的MovieRecommender将会把firstMovieCatalog注入:
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
4. 使用@Qualifier调整基于注解的自动注入
在基于类型的自动注入中,若出现了多个实例Bean,@Primary是一个高效的方式来决定注入哪个示例。若想在选择Bean的过程中拥有更多的控制,可以使用Spring的@Qualifier注解。
可以在指定参数上使用@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定义如下所示。标识值为“main”的Bean将会被标记为@Qualifier(“main”)的参数所注入:
<?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"/> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
默认情况下,标识值就是Bean的名称,因此可以直接定义Bean的id为“main”来代替内部的<qualifier>元素。
@Qualifier也可以作用于集合上,比如Set<MovieCatalog>,所有符合条件的元素都将会被注入到集合中。这就意味着,@Qualifier不必只能唯一标识一个元素。
5. 使用泛型作为自动注入的标识
除了@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>,可以自动注入Store接口,泛型就会被作为标志使用了:
@Autowired
private Store<String> s1; // <String>标志,注入stringStore
@Autowired
private Store<Integer> s2; // <Integer>标志,注入integerStore
需要注意的是,这里会查找实现了Store<String>接口的stringStore以及实现了Store<Integer>接口的integerStore。
当自动注入列表、Map和数组时,泛型也会生效:
//注入所有实现了Store<Integer>接口的Bean,实现了Store<String>的Bean不会被注入
@Autowired
private List<Store<Integer>> s;
6. 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>
7. @Resource
Spring也支持使用JSR-250的@Resource注解注入,它可以作用于字段或Bean属性的setter方法上。
@Resource有个name属性,默认情况下,Spring把name的值作为Bean的名称进行Bean注入。若name值缺省,则使用自动生成的name属性。跟@Autowired类似,当找到多个候选Bean时,@Resource查找一个主要的类型进行匹配。
8. @PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不仅可以识别@Resource注解,也可以识别JSR-250生命周期注解,请看电影缓存的例子:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
//初始化之前存入电影
}
@PreDestroy
public void clearMovieCache() {
//销毁之前删除电影
}
}