概述java
注释配置相对于 XML 配置具备不少的优点:程序员
它能够充分利用 Java 的反射机制获取类结构信息,这些信息能够有效减小配置的工做。如使用 JPA 注释配置 ORM 映射时,咱们就不须要指定 PO 的属性名、类型等信息,若是关系表字段和 PO 属性名、类型都一致,您甚至无需编写任务属性映射信息——由于这些信息均可以经过 Java 反射机制获取。spring
注释和 Java 代码位于一个文件中,而 XML 配置采用独立的配置文件,大多数配置信息在程序开发完成后都不会调整,若是配置信息和 Java 代码放在一块儿,有助于加强程序的内聚性。而采用独立的 XML 配置文件,程序员在编写一个功能时,每每须要在程序文件和配置文件中不停切换,这种思惟上的不连贯会下降开发效率。ide
所以在不少状况下,注释配置比 XML 配置更受欢迎,注释配置有进一步流行的趋势。Spring 2.5 的一大加强就是引入了不少注释类,如今您已经可使用注释配置完成大部分 XML 配置的功能。在这篇文章里,咱们将向您讲述使用注释进行 Bean 定义和依赖注入的内容。函数
原来咱们是怎么作的测试
在使用注释配置以前,先来回顾一下传统上是如何配置 Bean 并完成 Bean 之间依赖关系的创建。下面是 3 个类,它们分别是 Office、Car 和 Boss,这 3 个类须要在 Spring 容器中配置为 Bean:ui
Office 仅有一个属性:this
清单 1. Office.javaspa
package com.baobaotao; public class Office { private String officeNo =”001”; //省略 get/setter @Override public String toString() { return "officeNo:" + officeNo; } }
Car 拥有两个属性:code
清单 2. Car.java
package com.baobaotao; public class Car { private String brand; private double price; // 省略 get/setter @Override public String toString() { return "brand:" + brand + "," + "price:" + price; } }
Boss 拥有 Office 和 Car 类型的两个属性:
清单 3. Boss.java
package com.baobaotao; public class Boss { private Car car; private Office office; // 省略 get/setter @Override public String toString() { return "car:" + car + "\n" + "office:" + office; } }
咱们在 Spring 容器中将 Office 和 Car 声明为 Bean,并注入到 Boss Bean 中:下面是使用传统 XML 完成这个工做的配置文件 beans.xml:
清单 4. beans.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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="boss" class="com.baobaotao.Boss"> <property name="car" ref="car"/> <property name="office" ref="office" /> </bean> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="002"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 红旗 CA72"/> <property name="price" value="2000"/> </bean> </beans>
当咱们运行如下代码时,控制台将正确打出 boss 的信息:
清单 5. 测试类:AnnoIoCTest.java
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class AnnoIoCTest { public static void main(String[] args) { String[] locations = {"beans.xml"}; ApplicationContext ctx = new ClassPathXmlApplicationContext(locations); Boss boss = (Boss) ctx.getBean("boss"); System.out.println(boss); } }
使用 @Autowired 注释
Spring 2.5 引入了 @Autowired
注释,它能够对类成员变量、方法及构造函数进行标注,完成自动装配的工做。来看一下使用@Autowired
进行成员变量自动注入的代码:
清单 6. 使用 @Autowired 注释的 Boss.java
package com.baobaotao; import org.springframework.beans.factory.annotation.Autowired; public class Boss { @Autowired private Car car; @Autowired private Office office; … }
Spring 经过一个 BeanPostProcessor
对 @Autowired
进行解析,因此要让@Autowired
起做用必须事先在 Spring 容器中声明AutowiredAnnotationBeanPostProcessor
Bean。
清单 7. 让 @Autowired 注释工做起来
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <!-- 该 BeanPostProcessor 将自动起做用,对标注 @Autowired 的 Bean 进行自动注入 --> <bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/> <!-- 移除 boss Bean 的属性注入配置的信息 --> <bean id="boss" class="com.baobaotao.Boss"/> <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 红旗 CA72"/> <property name="price" value="2000"/> </bean> </beans>
这样,当 Spring 容器启动时,AutowiredAnnotationBeanPostProcessor
将扫描 Spring 容器中全部 Bean,当发现 Bean 中拥有@Autowired
注释时就找到和其匹配(默认按类型匹配)的 Bean,并注入到对应的地方中去。
按照上面的配置,Spring 将直接采用 Java 反射机制对 Boss 中的 car
和 office
这两个私有成员变量进行自动注入。因此对成员变量使用@Autowired
后,您大可将它们的 setter 方法(setCar()
和 setOffice()
)从 Boss 中删除。
固然,您也能够经过 @Autowired
对方法或构造函数进行标注,来看下面的代码:
清单 8. 将 @Autowired 注释标注在 Setter 方法上
package com.baobaotao; public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired public void setOffice(Office office) { this.office = office; } … }
这时,@Autowired
将查找被标注的方法的入参类型的 Bean,并调用方法自动注入这些 Bean。而下面的使用方法则对构造函数进行标注:
清单 9. 将 @Autowired 注释标注在构造函数上
package com.baobaotao; public class Boss { private Car car; private Office office; @Autowired public Boss(Car car ,Office office){ this.car = car; this.office = office ; } … }
因为 Boss()
构造函数有两个入参,分别是 car
和 office
,@Autowired
将分别寻找和它们类型匹配的 Bean,将它们做为Boss(Car car ,Office office)
的入参来建立 Boss
Bean。
当候选 Bean 数目不为 1 时的应对方法
在默认状况下使用 @Autowired
注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出BeanCreationException
异常,并指出必须至少拥有一个匹配的 Bean。咱们能够来作一个实验:
清单 10. 候选 Bean 数目为 0 时
<?xml version="1.0" encoding="UTF-8" ?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd "> <bean class="org.springframework.beans.factory.annotation. AutowiredAnnotationBeanPostProcessor"/> <bean id="boss" class="com.baobaotao.Boss"/> <!-- 将 office Bean 注释掉 --> <!-- <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean>--> <bean id="car" class="com.baobaotao.Car" scope="singleton"> <property name="brand" value=" 红旗 CA72"/> <property name="price" value="2000"/> </bean> </beans>
因为 office
Bean 被注释掉了,因此 Spring 容器中将没有类型为 Office
的 Bean 了,而 Boss 的office
属性标注了 @Autowired
,当启动 Spring 容器时,异常就产生了。
当不能肯定 Spring 容器中必定拥有某个类的 Bean 时,能够在须要自动注入该类 Bean 的地方可使用 @Autowired(required = false)
,这等于告诉 Spring:在找不到匹配 Bean 时也不报错。来看一下具体的例子:
清单 11. 使用 @Autowired(required = false)
package com.baobaotao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; public class Boss { private Car car; private Office office; @Autowired public void setCar(Car car) { this.car = car; } @Autowired(required = false) public void setOffice(Office office) { this.office = office; } … }
固然,通常状况下,使用 @Autowired
的地方都是须要注入 Bean 的,使用了自动注入而又容许不注入的状况通常仅会在开发期或测试期碰到(如为了快速启动 Spring 容器,仅引入一些模块的 Spring 配置文件),因此@Autowired(required = false)
会不多用到。
和找不到一个类型匹配 Bean 相反的一个错误是:若是 Spring 容器中拥有多个候选 Bean,Spring 容器在启动时也会抛出 BeanCreationException
异常。来看下面的例子:
清单 12. 在 beans.xml 中配置两个 Office 类型的 Bean
… <bean id="office" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> <bean id="office2" class="com.baobaotao.Office"> <property name="officeNo" value="001"/> </bean> …
咱们在 Spring 容器中配置了两个类型为 Office
类型的 Bean,当对 Boss 的 office
成员变量进行自动注入时,Spring 容器将没法肯定到底要用哪个 Bean,所以异常发生了。
Spring 容许咱们经过 @Qualifier
注释指定注入 Bean 的名称,这样歧义就消除了,能够经过下面的方法解决异常:
清单 13. 使用 @Qualifier 注释指定注入 Bean 的名称
@Autowired public void setOffice(@Qualifier("office")Office office) { this.office = office; }
@Qualifier("office")
中的 office
是 Bean 的名称,因此 @Autowired
和@Qualifier
结合使用时,自动注入的策略就从 byType 转变成 byName 了。@Autowired
能够对成员变量、方法以及构造函数进行注释,而@Qualifier
的标注对象是成员变量、方法入参、构造函数入参。正是因为注释对象的不一样,因此 Spring 不将 @Autowired
和@Qualifier
统一成一个注释类。下面是对成员变量和构造函数入参进行注释的代码:
对成员变量进行注释:
清单 14. 对成员变量使用 @Qualifier 注释
public class Boss { @Autowired private Car car; @Autowired @Qualifier("office") private Office office; … }
对构造函数入参进行注释:
清单 15. 对构造函数变量使用 @Qualifier 注释
public class Boss { private Car car; private Office office; @Autowired public Boss(Car car , @Qualifier("office")Office office){ this.car = car; this.office = office ; } }
@Qualifier
只能和 @Autowired
结合使用,是对 @Autowired
有益的补充。通常来说,@Qualifier
对方法签名中入参进行注释会下降代码的可读性,而对成员变量注释则相对好一些。
使用 JSR-250 的注释
Spring 不但支持本身定义的 @Autowired
的注释,还支持几个由 JSR-250 规范定义的注释,它们分别是 @Resource
、@PostConstruct
以及 @PreDestroy
。
@Resource
@Resource
的做用至关于 @Autowired
。@Resource
有两个属性是比较重要的,分别是 name 和 type,Spring 将@Resource
注释的 name 属性解析为 Bean 的名字,而 type 属性则解析为 Bean 的类型。因此若是使用 name 属性,则使用 byName 的自动注入策略,而使用 type 属性时则使用 byType 自动注入策略。若是既不指定 name 也不指定 type 属性,这时将经过反射机制使用 byType 自动注入策略。
Resource 注释类位于 Spring 发布包的 lib/j2ee/common-annotations.jar 类包中,所以在使用以前必须将其加入到项目的类库中。来看一个使用@Resource
的例子:
清单 16. 使用 @Resource 注释的 Boss.java
package com.baobaotao; import javax.annotation.Resource; public class Boss { // 自动注入类型为 Car 的 Bean @Resource private Car car; // 自动注入 bean 名称为 office 的 Bean @Resource(name = "office") private Office office; }
通常状况下,咱们无需使用相似于 @Resource(type=Car.class)
的注释方式,由于 Bean 的类型信息能够经过 Java 反射从代码中获取。要让 JSR-250 的注释生效,除了在 Bean 类中标注这些注释外,还须要在 Spring 容器中注册一个负责处理这些注释的 BeanPostProcessor
:
<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"/>
CommonAnnotationBeanPostProcessor
实现了 BeanPostProcessor
接口,它负责扫描使用了 JSR-250 注释的 Bean,并对它们进行相应的操做。
@PostConstruct 和 @PreDestroy
Spring 容器中的 Bean 是有生命周期的,Spring 容许在 Bean 在初始化完成后以及 Bean 销毁前执行特定的操做,您既能够经过实现 InitializingBean/DisposableBean 接口来定制初始化以后 / 销毁以前的操做方法,也能够经过 <bean> 元素的 init-method/destroy-method 属性指定初始化以后 / 销毁以前调用的操做方法。关于 Spring 的生命周期,笔者在《精通 Spring 2.x—企业应用开发精解》第 3 章进行了详细的描述,有兴趣的读者能够查阅。
JSR-250 为初始化以后/销毁以前方法的指定定义了两个注释类,分别是 @PostConstruct 和 @PreDestroy,这两个注释只能应用于方法上。标注了 @PostConstruct 注释的方法将在类实例化后调用,而标注了 @PreDestroy 的方法将在类销毁以前调用。