Spring2.5中基于注释的IoC
基于注释(Annotation)的配置有越来越流行的趋势,Spring 2.5 顺应这种趋势,提供了完全基于注释配置 Bean、装配 Bean 的功能,您可以使用基于注释的 Spring IoC 替换原来基于 XML 的配置。
注释的优势
它可以充分利用 Java 的反射机制获取类结构信息,这些信息可以有效减少配置的工作。如使用 JPA 注释配置 ORM 映射时,我们就不需要指定 PO 的属性名、类型等信息,如果关系表字段和 PO 属性名、类型都一致,您甚至无需编写任务属性映射信息——因为这些信息都可以通过 Java 反射机制获取。
注释和 Java 代码位于一个文件中,而 XML 配置采用独立的配置文件,大多数配置信息在程序开发完成后都不会调整,如果配置信息和 Java 代码放在一起,有助于增强程序的内聚性。而采用独立的 XML 配置文件,程序员在编写一个功能时,往往需要在程序文件和配置文件中不停切换,这种思维上的不连贯会降低开发效率。
因此在很多情况下,注释配置比 XML 配置更受欢迎,注释配置有进一步流行的趋势。Spring 2.5 的一大增强就是引入了很多注释类,现在您已经可以使用注释配置完成大部分XML 配置的功能。在这篇文章里,我们将向您讲述使用注释进行 Bean 定义和依赖注入的内容。
原来我们是怎么做的
UserDAO.java:
- package com.tanlan.springdemo;
- /**
- * UserDAO接口
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- public interface UserDAO {
- public void addUser();
- }
UserJDBCDAO.java:
- package com.tanlan.springdemo;
- /**
- * UserDAO的JDBC实现
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- public class UserJDBCDAO implements UserDAO {
- public void addUser() {
- System.out.println("使用JDBC增加用户信息!");
- }
- }
UserService.java:
- package com.tanlan.springdemo;
- /**
- * User业务逻辑处理类
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- public class UserService {
- private UserDAO userDAO;
- public void addUser() {
- userDAO.addUser();
- }
- public UserDAO getUserDAO() {
- return userDAO;
- }
- public void setUserDAO(UserDAO userDAO) {
- this.userDAO = userDAO;
- }
- }
配置文件 spring.xml:
- <?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
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <bean id="userJdbcDAO" class="com.tanlan.springdemo.UserJDBCDAO"></bean>
- <bean id="userService" class="com.tanlan.springdemo.UserService">
- <property name="userDAO" ref="userJdbcDAO"></property>
- </bean>
- </beans>
测试类Test.java:
- package com.tanlan.springdemo;
- import org.springframework.context.ApplicationContext;
- import org.springframework.context.support.ClassPathXmlApplicationContext;
- public class Test {
- public static void main(String[] args) {
- String[] files = { "spring.xml" };
- ApplicationContext context = new ClassPathXmlApplicationContext(files);
- UserService userService = (UserService) context.getBean("userService");
- userService.addUser();
- }
- }
使用 @Autowired 注释
Spring 2.5 引入了 @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。
对UserService.java的改进:
- package com.tanlan.springdemo;
- import org.springframework.beans.factory.annotation.Autowired;
- /**
- * User业务逻辑处理类
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- public class UserService {
- @Autowired
- private UserDAO userDAO;
- public void addUser() {
- userDAO.addUser();
- }
- }
这个类甚至可以省略getter/setter.
配置文件spring.xml的改进:
- <?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
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean>
- <bean id="userJdbcDAO" class="com.tanlan.springdemo.UserJDBCDAO"></bean>
- <bean id="userService" class="com.tanlan.springdemo.UserService"></bean>
- </beans>
Spring 通过一个 BeanPostProcessor 对 @Autowired 进行解析,所以要让 @Autowired 起作用必须事先在 Spring 容器中声明 AutowiredAnnotationBeanPostProcessor Bean。
UserService类的属性不需要配置了。
当候选 Bean 数目不为 1 时的应对方法
在默认情况下使用 @Autowired 注释进行自动注入时,Spring 容器中匹配的候选 Bean 数目必须有且仅有一个。当找不到一个匹配的 Bean 时,Spring 容器将抛出BeanCreationException 异常,并指出必须至少拥有一个匹配的 Bean。
和找不到一个类型匹配 Bean 相反的一个错误是:如果 Spring 容器中拥有多个候选Bean,Spring 容器在启动时也会抛出 BeanCreationException 异常。
Spring 允许我们通过 @Qualifier 注释指定注入 Bean 的名称
加入现在多了一个UserDAO的实现类
UserHibernateDAO.java:
- package com.tanlan.springdemo;
- /**
- * UserDAO的Hibernate实现
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- public class UserHibernateDAO implements UserDAO {
- public void addUser() {
- System.out.println("使用Hibernate增加用户信息!");
- }
- }
更新spring.xml:
- <?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
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <bean class="org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor"></bean>
- <bean id="userJdbcDAO" class="com.tanlan.springdemo.UserJDBCDAO"></bean>
- <bean id="userHibernateDAO" class="com.tanlan.springdemo.UserHibernateDAO"></bean>
- <bean id="userService" class="com.tanlan.springdemo.UserService"></bean>
- </beans>
当Spring自动找寻UserDAO类型的类时,会找到两个符合要求的类:
No unique bean of type [com.tanlan.springdemo.UserDAO] is defined: expected single matching bean but found 2: [userJdbcDAO, userHibernateDAO]
需要改进UserService.java:
- package com.tanlan.springdemo;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- /**
- * User业务逻辑处理类
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- public class UserService {
- @Autowired
- @Qualifier("userJdbcDAO")
- private UserDAO userDAO;
- public void addUser() {
- userDAO.addUser();
- }
- }
使用 <context:annotation-config/> 简化配置
Spring 2.1 添加了一个新的 context 的 Schema 命名空间,该命名空间对注释驱动、属性文件引入、加载期织入等功能提供了便捷的配置.
改进spring.xml:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context"
- 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
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <context:annotation-config></context:annotation-config>
- <bean id="userJdbcDAO" class="com.tanlan.springdemo.UserJDBCDAO"></bean>
- <bean id="userHibernateDAO" class="com.tanlan.springdemo.UserHibernateDAO"></bean>
- <bean id="userService" class="com.tanlan.springdemo.UserService"></bean>
- </beans>
注意命名空间:xmlns:context=http://www.springframework.org/schema/context的添加。
以及<context:annotation-config/>的使用。
使用 @Component
Spring 2.5 提供的 @Component 注释可以定义 Bean,从 XML 配置文件中完全移除 Bean 定义的配置。
改进UserJDBCDAO.java:
- package com.tanlan.springdemo;
- import org.springframework.stereotype.Component;
- /**
- * UserDAO的JDBC实现
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- @Component("userJdbcDAO")
- public class UserJDBCDAO implements UserDAO {
- public void addUser() {
- System.out.println("使用JDBC增加用户信息!");
- }
- }
改进UserHibernateDAO.java:
- package com.tanlan.springdemo;
- import org.springframework.stereotype.Component;
- /**
- * UserDAO的Hibernate实现
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- @Component("userHibernateDAO")
- public class UserHibernateDAO implements UserDAO {
- public void addUser() {
- System.out.println("使用Hibernate增加用户信息!");
- }
- }
改进UserService.java:
- package com.tanlan.springdemo;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.stereotype.Component;
- /**
- * User业务逻辑处理类
- *
- * @author tanlan tl_smile@163.com
- *
- * @date 2009-8-3
- */
- @Component
- public class UserService {
- @Autowired
- @Qualifier("userJdbcDAO")
- private UserDAO userDAO;
- public void addUser() {
- userDAO.addUser();
- }
- }
改进spring.xml:
- <?xml version="1.0" encoding="UTF-8"?>
- <beans xmlns="http://www.springframework.org/schema/beans"
- xmlns:context="http://www.springframework.org/schema/context"
- 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
- http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd">
- <context:component-scan base-package="com.tanlan.springdemo"></context:component-scan>
- </beans>
在使用 @Component 注释后,Spring 容器必须启用类扫描机制以启用注释驱动 Bean 定义和注释驱动 Bean 自动注入的策略.
<context:component-scan/> 的 base-package 属性指定了需要扫描的类包,类包及其递归子包中所有的类都会被处理.
注释配置和 XML 配置的适用场合
是否有了这些 IOC 注释,我们就可以完全摒除原来 XML 配置的方式呢?答案是否定的。有以下几点原因:
注释配置不一定在先天上优于 XML 配置。如果 Bean 的依赖关系是固定的,(如Service 使用了哪几个 DAO 类),这种配置信息不会在部署时发生调整,那么注释配置优于 XML 配置;反之如果这种依赖关系会在部署时发生调整,XML 配置显然又优于注释配置,因为注释是对 Java 源代码的调整,您需要重新改写源代码并重新编译才可以实施调整。
如果 Bean 不是自己编写的类(如 JdbcTemplate、SessionFactoryBean 等),注释配置将无法实施,此时 XML 配置是唯一可用的方式。
注释配置往往是类级别的,而 XML 配置则可以表现得更加灵活。比如相比于@Transaction 事务注释,使用 aop/tx 命名空间的事务配置更加灵活和简单。
所以在实现应用中,我们往往需要同时使用注释配置和 XML 配置,对于类级别且不会发生变动的配置可以优先考虑注释配置;而对于那些第三方类以及容易发生调整的配置则应优先考虑使用 XML 配置。Spring 会在具体实施 Bean 创建和 Bean 注入之前将这两种配置方式的元信息融合在一起。