使用 Spring 2.5 注释驱动的 IoC 功能(1)
概述
注释配置相对于 XML 配置具有很多的优势:
它可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作。如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO 的属性名、类型等信息,如果关系表字段和 PO 属性名、类型都一致,您甚至无需编写任务属性映射信息——因为这些信息都可以通过 Java 反射机制获取。
注释和 Java 代码位于一个文件中,而 XML 配置采用独立的配置文件,大多数配置信息在程序开发完成后都不会调整,如果配置信息和 Java 代码放在一起,有助于增强程序的内聚性。而采用独立的 XML 配置文件,程序员在编写一个功能时,往往需要在程序文件和配置文件中不停切换,这种思维上的不连贯会降低开发效率。
因此在很多情况下,注释配置比 XML 配置更受欢迎,注释配置有进一步流行的趋势。Spring 2.5 的一大增强就是引入了很多注释类,现在您已经可以使用注释配置完成大部分 XML 配置的功能。在这篇文章里,我们将向您讲述使用注释进行 Bean 定义和依赖注入的内容。
?
?
?
?
?
原来我们是怎么做的
在使用注释配置之前,先来回顾一下传统上是如何配置 Bean 并完成 Bean 之间依赖关系的建立。下面是 3 个类,它们分别是 Office、Car 和 Boss,这 3 个类需要在 Spring 容器中配置为 Bean:
Office 仅有一个属性:
清单 1. Office.java
???????????????
package com.baobaotao;
public class Office {
??? private String officeNo =”001”;
??? //省略 get/setter
??? @Override
??? public String toString() {
??????? return "officeNo:" + officeNo;
??? }
}
?
Car 拥有两个属性:
清单 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" ref="car"/>
??????? <property name="office" ref="office" />
??? </bean>
??? <bean id="office" value="002"/>
??? </bean>
??? <bean id="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);
??? }
}
?
这说明 Spring 容器已经正确完成了 Bean 创建和装配的工作。
?
?
?
?
?
使用 @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 value="001"/>
??? </bean>
??? <bean id="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 value="001"/>
??? </bean>-->
??? <bean id="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" value="001"/>
</bean>
<bean id="office2" 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 对方法签名中入参进行注释会降低代码的可读性,而对成员变量注释则相对好一些。
?
?