🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
## 4.9 基于注解的容器配置 配置Spring时,注解要比XML更好吗? 基于注解配置的介绍就提出了这样的问题,这种方法要比XML‘更好’吗?简短的回答就是具体问题具体分析。完整的答案就是每种方法都有它的利与弊,通常是让开发 人员来决定使用哪种策略更适合使用。由于定义它们的方式,注解在它们的声明中提供了大量的上下文,使得配置更加简短和简洁。然而,XML更擅长装配组件,而不需要触碰它们源代码或重新编译。一些开发人员更喜欢装配源码而其他人认为被注解的类不再 是POJO了,此外,配置变得分散并且难以控制。 无论怎么去线则,Spring都可以容纳两种方式,甚至是它们的混合体。最值得指出 的是通过JavaConfig(4.12节)选择,Spring允许以非侵入式的方式来使用注解,而不需 要触碰目标组件的源代码和工具,所有的配置方式都是[SpringSource Tool Suite](http://www.springsource.com/products/sts)所支持的。 作为 XML 配置的另外一种选择,依靠字节码元数据的基于注解的配置来装配组件代替 了尖括号式的声明。作为使用 XML 来表述 bean 装配的替换,开发人员可以将配置信息移入 到组件类本身中,在相关的类,方法或字段声明上使用注解。正如在 4.8.1.2 节,“示例: RequiredAnnotationBeanPostProcessor”所提到的,使用 BeanPostProcessor 来连接注解是扩展 Spring IoC 容器的一种常用方式。比如,Spring 2.0 引入的使用@Required(4.9.1 节)注解来强制所需属性的可能性。在 Spring 2.5 中,可以使用相同的处理方法来驱 动 Spring 的依赖注入。从本质上来说,@Autowired 注解提供了在 4.4.5 节,“自动装配协 作者”中描述的相同能力,但却有更细粒度的控制和更广泛的适用性。Spring 2.5 也添加了 对 JSR-250 注解的支持,比如@Resource,@PostConstruct 和@PreDestroy。Spring 3.0 添加了对 JSR-330(对 Java 的依赖注入)注解的支持,包含在 javax.inject 包下,比如@Inject,@Qualifier,@Named 和@Provider,当 JSR330 的 jar 包在类路径下时就可以使用。使 用这些注解也需要在 Spring 容器中注册特定的 BeanPostProcessor。 > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 注解注入会在 XML 注入之前执行,因此通过两种方式,那么后面的配置会覆盖前面装 配的属性。 一如往常,你可以注册它们作为独立的 bean,但是它们也可以通过包含下面的基于 XML的 Spring 配置代码片段被隐式地注册(注意要包含 context 命名空间): ``` <?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/bean[s http://www.springframework.org/schema/beans/spring -beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> </beans> ``` ( 隐 式 注 册 的 后 处 理 器 包 含 AutowiredAnnotationBeanPostProcessor , CommonAnnotationBeanPostProcessor , PersistenceAnnotationBeanPostProcessor , 以 及 上 述 的 RequiredAnnotationBeanPostProcessor) > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > `<context:annotation-config/>`仅仅查找定义在同一上下文中的 bean 的注解。 这就意味着,如果你为 DispatcherServlet 将`<context:annotation-config/>`放 置在 WebApplicationContext 中,那么它仅仅检查控制器中的@Autowired bean,而 不是你的服务层 bean,可以参看 16.2 节,“DispatcherServlet”来查看更多信息。 ### 4.9.1 @Required @Required 注解应用于 bean 属性的 setter 方法,就向下面这个示例: ``` public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... } ``` 这个注解只是表明受影响的 bean 的属性必须在 bean 的定义中或者是自动装配中通过明确的属性值在配置时来填充。如果受影响的 bean 属性没有被填充,那么容器就会抛出异 常;这就允许了急切而且明确的失败,要避免 NullPointerException。我们推荐你放 置断言到 bean 的类中,比如,放置到初始化方法中。当你在容器外部使用类时,这么来做 是强制那些所需的引用和值。 ### 4.9.2 @Autowired 和@Inject 正如预期的那样,你可以使用@Autowired 注解到“传统的”setter 方法中: 注意在下面的示例中,JSR330 的@Inject 注解可以用于代替 Spring 的@Autowired。没有必须的属性,不像 Spring 的@Autowired 注解那样,如果要注入的值是可选的话,要有一个 required 属性来表示。如果你将 JSR330 的 JAR 包放置到类路径下的话,这种行为就会自 动开启。 ``` 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; } // ... } ``` 也可以提供 ApplicationContext 中特定类型的所有 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; } // ... } ``` 甚至特定类型的 Map 也可以自动装配,但是期望的键的类型是 String 的。Map 值会 包含所有期望类型的 bean,而键会包含对应 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; } // ... } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 每一个类中仅有一个被注解方法可以标记为必须的,但是多个非必须的构造方法可 以被注解。在那种情况下,每个构造方法都要考虑到而且 Spring 使用依赖可以被满足 的那个构造方法,那就是参数最多的那个构造方法。 @Autowired 需要的属性推荐使用@Required 注解。所需的表示了属性对于自动 装配目的不是必须的,如果它不能被自动装配,那么属性就会忽略了。另一方面, @Required 更健壮一些,它强制了由容器支持的各种方式的属性设置。如果没有注入任何值,就会抛出对应的异常。 你也可以针对我们熟知的解决依赖关系的接口来使用@Autowired:BeanFactory,ApplicationContext , Environment , ResourceLoader ,ApplicationEventPublisher 和 MessageSource。这些接口和它们的扩展接口,比 如 ConfigurableApplicationContext 或 ResourcePatternResolver 也会被自动解析,而不需要特殊设置的必要。 ``` public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > @Autowired , @Inject , @Resource 和 @Value 注 解 是 由 Spring 的 BeanPostProcessor 实现类来控制的,反过来告诉你你不能在 BeanPostProcessor 或 BeanFactoryPostProcessor 类型(任意之一)应用这些注解。这些类型必须明确地 通过 XML 或使用 Spring 的@Bean 方法来‘装配’。 ### 4.9.3 使用限定符来微调基于注解的自动装配 因为通过类型的自动装配可能导致多个候选者,那么在选择过程中通常是需要更多的控 制的。达成这个目的的一种做法就是 Spring 的@Qualifier 注解。你可以用特定的参数来 关联限定符的值,缩小类型的集合匹配,那么特定的 bean 就为每一个参数来选择。最简单 的情形,这可以是普通描述性的值: > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > JSR 330 的@Qualifier 注解仅仅能作为元注解来用,而不像 Spring 的@Qualifier 可以 使用字符串属性来在多个注入的候选者之间区别,并可以放在注解,类型,字段,方法,构 造方法和参数中。 ``` 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-3.0.xsd http://www.springframework.org/schema/contex)t http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier value="main"/> <!-- 注入这个bean需要的任何依赖 --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> <!-- 注入这个bean需要的任何依赖 --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans> ``` 对于后备匹配,bean 名称被认为是默认的限定符值。因此你可以使用 id 为“main”来 定义 bean,来替代嵌套的限定符元素,这也会达到相同的匹配结果。然而,尽管你使用这 种规约来通过名称参照特定的 bean,@Autowired 从根本上来说就是关于类型驱动注入和可 选语义限定符的。这就是说限定符的值,即便有 bean 的名称作为后备,通常在类型匹配时 也会缩小语义;它们不会在语义上表达对唯一 bean 的 id 的引用。好的限定符的值是“main” 或“EMEA”或“persistent”,特定组件的表达特性是和 bean 的 id 独立的,在比如前面示例 之一的匿名 bean 的情况下它是可能自动被创建的。 限定符也可以用于类型集合,正如上面讨论过的,比如 `Set<MovieCatalog>`。在这 种情况下,根据声明的限定符,所有匹配的 bean 都会被注入到集合中。这就说明了限定符 不必是唯一的;它们只是构成了筛选条件。比如,你可以使用相同的限定符“action”来定 义多个 MovieCatalog bean;它们全部都会通过@Qualifier("action")注解注入到 `Set<MovieCatalog>`中。 > ![](https://box.kancloud.cn/2016-01-25_56a58526e359f.gif) > 提示 > 如果你想通过名称来表达注解驱动注入,主要是不使用@Autowired,即便在技术上来说能够通过@Qualifier 值指向一个 bean 的名称。相反 ,使用 JSR-250 的 @Resource 注解,在语义上定义了通过它的唯一名称去确定一个具体的目标组件,声 明的类型和匹配过程无关。 由于这种语义区别的特定后果,bean 本身被定义为集合或 map 类型,是不能通过 @Autowired 来进行注入的,因为类型匹配用于它们不太合适。对于这样的 bean 使用 @Resource,通过唯一的名称指向特定的集合或 map 类型的 bean。 @Autowired 可以用于字段,构造方法和多参数方法,在参数级允许通过限定符 注解缩小。相比之下,@Resource 仅支持字段和仅有一个参数的 bea n 属性的 setter 方法。因此,如果你的注入目标是构造方法或多参数的方法,那么就坚持使用限定符。 你可以创建你自定义的限定符注解。只需定义一个注解并在你的定义中提供 @Qualifier 注解即可。 注意你可以以下面描述的方式来使用 JSR 330 的@Qualifier 注解,用于替换 Spring 的 @Qualifier 注解。如果在类路径中有 JSR 330 的 jar 包,那么这种行为是自动开启的。 ``` @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 值来匹配你自定义的限定符注解。这种类型匹配是基于 注解类的完全限定名。否则,作为一种简便的形式,如果没有名称冲突存在的风险,你可以 使用短类名。这两种方法都会在下面的示例中来展示。 ``` <?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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="Genre" value="Action"/> <!— 注入这个bean所需要的任何依赖 --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="example.Genre" value="Comedy"/> <!— 注入这个bean所需要的任何依赖 --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans> ``` 在 4.10 节,“类路径扫描和管理的组件”中,你会看到基于注解的替代,在 XML 中来 提供限定符元数据。特别是在 4.10.7 节,“使用注解提供限定符元数据”中。 在一些示例中,使用无值的注解就足够了。这当注解服务于多个通用目的时是很有用的, 而且也可以用于集中不同类型的依赖关系。比如,当没有因特网连接时,你可以提供脱机目 录来由于搜索。首先定义简单的注解: ``` @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { } ``` 之后将注解添加到要被自动装配的字段或属性上: ``` public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... } ``` 现在来定义 bean 的限定符的 type: ``` <bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!— 注入这个bean所需要的任何依赖 --> </bean> ``` 你也可以定义自定义的限定符注解来接受命名属性,除了或替代简单的 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 的元属性可能用 于替代`<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-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Action"/> </qualifier> <!— 注入这个bean所需要的任何依赖 --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Comedy"/> </qualifier> <!— 注入这个bean所需要的任何依赖 --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="DVD"/> <meta key="genre" value="Action"/> <!— 注入这个bean所需要的任何依赖 --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="BLURAY"/> <meta key="genre" value="Comedy"/> <!— 注入这个bean所需要的任何依赖 --> </bean> </beans> ``` ### 4.9.4 CustomAutowireConfigurer [CustomAutowireConfigurer](http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/beans/factory/annotation/CustomAutowireConfigurer.html) 是 BeanFactoryPostProcessor 的一种,它使得 你可以注册你自己定义的限定符注解类型,即便它们没有使用 Spring 的@Qualifier 注解 也是可以的。 ``` <bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAu towireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomQualifier</value> </set> </property> </bean> ``` AutowireCandidateResolver 的特别实现类对基于 Java 版本的应用程序上下文激 活。在 Java 5 之前的版本中,限定符注解是不支持的,因此自动装配候选者仅由每个 bean 定义中的 autowire-candidate 值来决 定,还有 在`<beans/>`元 素中的任 何可用的 default-autowire-candidates 模 式 也 是 可 以 的 。 在 Java 5 之 后 的 版 本 中 , @Qualifier 注解的存在还有任意使用 CustomAutowireConfigurer 注册的自定义注解也将发挥作用。 不管 Java 的版本,当多个 bean 作为自动装配的候选者,决定“主要”候选者的方式也 是相同的:如果候选者中一个 bean 的定义有 primary 属性精确地设置为 true,那么它就会被选择。 ### 4.9.5 @Resource Spring 也支持使用 JSR 250 的@Resource 注解在字段或 bean 属性的 setter 方法上的注 入。这在 Java EE 5 和 6 中是一个通用的模式,比如在 JSF 1.2 中管理的 bean 或 JAX-WS 2.0 端点。Spring 也为其所管理的对象支持这种模式。 @Resource 使用名称属性,默认情况下 Spring 解释这个值作为要注入的 bean 的名称。 换句话说,如果遵循 by-name 语义,正如在这个示例所展示的: ``` public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } ``` 如果没有明确地指定名称,那么默认的名称就从字段名称或 setter 方法中派生出来。以字段为例,它会选用字段名称;以 setter 方法为例,它会选用 bean 的属性名称。所以下面 的示例中有名为“movieFinder”的 bean 通过 setter 方法来注入: ``` public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 使用注解提供的名称被感知 CommonAnnotationBeanPostProcessor 的 ApplicationContext 解析成 bean 的名称。名称可以通过 JNDI 方式来解析,只要你明确 地配置了 Spring 的 [SimpleJndiBeanFactory](http://static.springframework.org/spring/docs/3.0.x/javadoc-api/org/springframework/jndi/support/SimpleJndiBeanFactory.html)。然而,还是推荐你基于默认行为并仅使用 Spring 的 JNDI 查找能力来保存间接的水平。 在使用@Resource 并没有 明确指 定名称的 独占情况 下,和 @Autowired 相似, @Resource 发现主要类型匹配,而不是特定名称 bean 并解析了熟知的可解析的依赖关系: BeanFactory , ApplicationContext , ResourceLoader , ApplicationEventPublisher,MessageSource 和接口。 因 此 , 在 下 面 的 示 例 中 , customerPreferenceDao 字 段 首 先 寻 找 名 为 customerPreferenceDao 的 bean,之后回到匹配 CustomerPreferenceDao 的主类型。 “context”字段基于已知的可解析的依赖类型 ApplicationContext 注入。 ``` public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; public MovieRecommender() { } // ... } ``` ### 4.9.6 @PostConstruct 和@PreDestroy CommonAnnotationBeanPostProcessor 不但能识别@Resource 注解,而且还能 识别 JSR-250 生命周期注解。在 Spring 2.5 中引入,对这些注解的支持提供了在初始化回调( 4.6.1.1 节 ) 和 销 毁 回 调 ( 4.6.1.2 节 ) 中 的 另 一 种 选 择 。 只 要 CommonAnnotationBeanPostProcessor 在 Spring 的 ApplicationContext 中注册, 一个携带这些注解之一的方法就同时被调用了,和 Spring 生命周期接口方法或明确地声明回调方法相对应。在下面的示例中,在初始化后缓存会预先填充,在销毁后会清理。 ``` public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // 在初始化时填充movie cache... } @PreDestroy public void clearMovieCache() { // 在销毁后清理movie cache... } } ``` > ![](https://box.kancloud.cn/2016-01-25_56a58526bb420.gif) > 注意 > 关于组合多个生命周期机制影响的细节内容,请参考 4.6.1.4 节,“组合生命周期机制”。