注解比XML更适合配置Spring吗?java
引入基于注解的配置提出了这种方法是否比XML更好的问题。简短的回答是视状况而定。详细的回答是每一个方法都有它的优缺点,而且一般由开发者来决定哪一种策略更适合他们。因为其定义的方式,注解在其声明中提供了大量的上下文,这使得配置更短、更简洁。然而,XML的优点是装配组件的时候并不接触它们的源码或者从新编译它们。一些开发者喜欢装配更靠近源码,而另外一些开发者认为注解的类再也不是POJO而且所以配置变得分散、难以控制。算法
不管如何选择,Spring均可以容纳两种风格甚至混合使用它们。值得指出的是经过Java配置,Spring容许以非侵入的方式使用注解,而不须要触碰目标组件源代码。spring
替代XML的基于注解的配置依靠字节码元数据装配组件来替代尖括号声明。开发者经过使用相关类、方法或字段声明上的注解将配置移动到组件类自己。正如在“示例:RequiredAnnotationBeanPostProcessor”一节中所提到的,使用BeanPostProcessor结合注释是扩展Spring IoC容器的经常使用手段。例如,Spring 2.0引入了使用@Required注解强制须要属性的可能性。Spring 2.5使得遵循相同的方法来驱动Spring依赖注入成为可能。本质上,@Autowired注解提供了与自动装配中所述相同的功能,可是具备更细粒度的控制和更普遍的适用性。Spring 2.5还加入了对于JSR-250注解的支持例如@PostConstruct和@PreDestory。Spring 3.0 加入了对JSR-330(Java的依赖注入)注解包括javax.inject包中@Injec和@Name的支持。数组
注解注入先于XML注入进行,所以使用两种方式装配属性后者会覆盖前者。缓存
能够注册它们为单独的bean定义,但也能够经过在基于XML的Spring配置中包含如下标签来隐式注册(注意要包含context命名空间):app
<?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>
(隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor和上文说起的RequiredAnnotationBeanPostProcessor。)函数
context:annotation-config/仅在与其定义相同的应用程序上下文中的bean上查找注释。这意味着若是将context:annotation-config/放入DispathcerServlet中的WebApplicationContext,它仅仅检查controller中的@Autowired bean,而不坚持service。ui
使用@Required注解应用于bean属性的setter方法,例以下面的例子:this
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
这个注解只是简单的表示受影响的bean属性必须在配置时经过bean定义或者自动装配中的显示属性值来填充。若是未注入受影响的bean属性,容器将抛出异常。这能够产生当即和明确的错误,从而在后面避免NullPointerException异常等。仍然建议将断言放入bean类自己,例如将其放入init方法中。这样作即便在容器以外使用类时也会执行这些必须的引用。spa
JSR 330 的@Inject注解能够用来替代下面例子中的Spring @Autowired注解。
能够将@Autowired注解应用于构造函数:
public class MovieRecommender { private final CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } }
从Spring Framework 4.3开始,若是目标bean只定义了一个构造函数,@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; } // ... }
还能够经过将注解添加到数组字段或方法上来注入容器中特定类型的全部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; } }
若是须要将数组和列表中的元素按指定的顺序排序,能够实现org.springframework.core.Ordered接口或者使用@Order或标准的@Priority注解。
甚至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; } // ... }
每一个class只有一个注解构造函数能够被标记为required,可是能够标记多个no-required构造函数。在这种状况下,每一个构造函数都被认为是候选者而且Spring使用最可靠的构造函数,其依赖性能够被知足,也就是具备最多参数的构造函数。
推荐使用@Autowired的required属性而不是@Required注解。required属性表示了属性对于自动装配目的不是必须的,若是它不能被自动装配,那么属性就会忽略了(???)。另外一方面,@Required更健壮一些,它强制了由容器支持的各类方式的属性设置。若是没有注入任何值,就会抛出对应的异常。
还能够在众所周知的可解析依赖关系的接口上使用@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 方法自动装配。
因为经过类型的自动装配可能会致使多个候选者,因此常常须要对选择处理作更多的控制。一种方法是使用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; }
相应的XML bean定义配置以下:
<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是一种有效的方式从许多实例中决定一个主要的候选者。当选择处理须要更多的控制时,可使用Spring的@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被注入到具备相同值的构造函数参数。
<?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的引用。好的限定值是"main"或者"EMEA"或者"persistent",它们表达指定组件的特征,而不依赖于bean的id,bean的id在匿名bean定义的状况下也许是自动建立的(???)。
限定值也应用于集合类型,例如Set<MovieCatalog>。在这种状况下,全部与声明的限定值匹配的bean被注入到集合中。这意味着限定值没必要惟一;它们仅仅构成过滤标准。例如,能够定义多个MovieCatalog bean使用相同的限定值"action",全部这些bean都会被注入到标注了@Qualifier("action")注解的Set<MovieCatalog>中。
若是打算使用按类型的注解驱动注入,请勿使用@Autowired,即便使用@Qualifier的值引用一个bean的名字在技术上是可行的。做为替代,使用JSR-250的@Resource注解,它在语义上经过惟一的名字定义了一个指定的目标组件,声明的类型与匹配过程无关。@Autowired拥有许多不一样的语义:在经过类型选择协做bean以后,指定的字符串限定值将尽在这些经过类型选出的候选者中被考虑,例如将“account”限定值与标记有相同限定值标签的bean相匹配。
对于bean自身被定义为集合/映射或者数组类型,@Resource经过惟一的名字指定特殊的集合或者数组bean是一个好的解决方案。即在4.3中,集合/映射或者数组类型也能够经过Spring的@Autowired的类型匹配算法进行匹配,只要元素的类型信息被@Bean方法的返回类型签名或者集合继承关系提供。在这种状况下,限定值能够被用于从相同类型集合中选择,像上文所述。
在4.3版本,@Autowired也将自身引用归入注入范围,即引用回目前注入的bean。注意自身注入是一种回退;正规的其它组件的依赖老是优先的。在这种意义下,自引用不参加常规候选的选择而且所以从不是优先的;另外一方面,它们老是有最低的优先级。在事件中,只能使用自引用做为最后的手段,即经过bean的事务代理在统一实例上调用其余方法:在这种状况下,考虑将受影响的方法分解为单独的委托bean(???)。另外,使用@Resource能够经过惟一的名字获得当前bean的代理。@Autowired应用与字段、构造函数和多参数方法,容许在参数级别经过限定值缩小范围。相比之下,@Resource仅仅支持字段和单一参数的bean属性setter方法。所以,若是注入目标是构造函数或多参数方法请使用限定值。
能够定义本身的限定值注解。定义一个注解并将其用@Qualifier标准便可。
@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定义的信息。可使用<qualifier/>标签做为<bean/>的子标签,而后指定type属性和value属性来匹配自定义的限定值注解。type匹配注解的彻底限定类名。或者,为了方便,在没有名称冲突的状况可使用短类名。两种方法都在下面例子中展现:
<?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>
在某些状况下,使用没有值的注解可能就足够了。当注释提供更通用的目的而且应用于跨不一样类型的依赖性时,这多是有用的。例如,可能会提供一个离线目录,当没有Internet链接可用时将被搜索。首先定义一个简单的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }
而后用注解注释那些要自动装配的字段或者属性:
public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... }
如今bean定义仅仅须要一个限定值类型:
<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!-- inject any dependencies required by this bean --> </bean>
还能够建立自定义的限定值注解,接受额外的name属性或者用name属性替代value属性。若是多个属性值限定了将要自动装配的字段或者参数,匹配全部属性值的bean定义才会成为候选者。做为例子,考虑下面的注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); }
在这个例子中Format是一个枚举类型:
public enum Format { VHS, DVD, BLURAY }
要自动装配的字段用自定义的限定注解注释并包含genre和format两个属性。
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; // ... }
最终,bean定义包含匹配的限定值。这个例子也展现bean meta标签能够替代<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 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 class MyConfiguration { @Bean public StringStore stringStore() { return new StringStore(); } @Bean public IntegerStore integerStore() { return new IntegerStore(); } }
假设上面的bean实现了一个范型接口,即Store<String>和Store<Integer>,可使用使用@Autowired注释Store接口而且范型会被用做限定值:
@Autowired private Store<String> s1; // <String> qualifier, injects the stringStore bean @Autowired private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
范型限定值也应用于自动装配列表、映射和数组:
// 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是一个BeanFactoryPostProcessor,它可使你注册本身定义的限定注解类型,甚至它们能够不被Spring的@Qualifier注解注释。
<bean id="CustomAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomerQualifier</value> </set> </property> </bean>
AutowireCandidateResolver经过如下信息决定自动装配候选者:
若是多个bean被限定为自动装配候选者,优先者按以下选出:若是其中一个bean定义将primary属性设置为true,则它将被选中。
Spring也支持使用JSR-250的@Resource注解注释字段或者bean属性的setter方法进行注入。这是Java EE 5和6中的常见方式,例如在JSF1.2中管理的bean和JAX-WS 2.0端点。Spring也Spring管理的对象支持这种模式。
@Resource有一个name属性,而且默认的将这个值解释为被注入bean的名字。换句话说,它符合by-name场景,正以下面的例子所示:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
若是没有明确的指定name,将从字段名或者setter方法派生出默认的名字。在字段状况下,将使用字段名;在setter方法的状况下,使用bean属性名。因此下面的例子将会把名为"movieFinder"的bean注入setter方法中:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
注解提供的名字是经过ApplicationConext的CommonAnnotationBeanPostProcessor解析的。能够经过JNDI解析名字,若是明确的配置了Spring的SimpleJndiBeanFactory。然而,建议依赖默认的行为而且简单的使用Spring的JNDI查找能力来保持解耦。
在没有明确指定名称的@Resource状况下,相似于@Autowired,@Resource查找优先级类型匹配取代一个指定名字的bean而且解析众所周知的可解析依赖关系:BeanFactory,ApplicationContext,ResourceLoader,ApplicationEventPublisher和MessageSource接口(就是说使用@Resource容器也会自动装配这些接口)。
所以在下面的例子中,customerPreferenceDao字段首先查找一个名为customerPreferenceDao的bean,而后回退到CutomerPreferenceDao的优先级类型匹配。“context”字段被注入基于已知的可解析依赖关系类型ApplicationContext。
public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; public MovieRecommender() { } // ... }
CommonAnnotationBeanPostProcessor不只识别@Resource注解也识别JSR-250的声明周期注解。这是在Spring2.5中引入的,对这些注释的支持为初始化回调和销毁回调中描述方法的提供了另外一种替代。只要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... } }