数据库的事务处理是在进行数据库应用开发中必须进行处理的一个问题。那么对于选择Hibernate作为持久层组件,了解Hibernate的事务处理机制就显得尤为重要了。
事务的基本概念
事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位。例如,银行转账工作:从一个账号扣款并使另一个账号增款,这两个操作要么都执行,要么都不执行。所以,应该把它们看成一个事务。事务是数据库维护数据一致性的单位,在每个事务结束时,都能保持数据一致性。
针对上面的描述可以看出,事务的提出主要是为了解决并发情况下保持数据一致性的问题。
事务具有以下4个基本特征。
● Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成功,要么全部失败。
● Consistency(一致性):只有合法的数据可以被写入数据库,否则事务应该将其回滚到最初状态。
● Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。
● Durability(持久性):事务结束后,事务处理的结果必须能够得到固化。
数据库肯定是要被广大客户所共享访问的,那么在数据库操作过程中很可能出现以下几种不确定情况。
● 更新丢失(Lost update):两个事务都同时更新一行数据,但是第二个事务却中途失败退出,导致对数据的两个修改都失效了。这是因为系统没有执行任何的锁操作,因此并发事务并没有被隔离开来。
● 脏读取(Dirty Reads):一个事务开始读取了某行数据,但是另外一个事务已经更新了此数据但没有能够及时提交。这是相当危险的,因为很可能所有的操作都被回滚。
● 不可重复读取(Non-repeatable Reads):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。例如,在两次读取的中途,有另外一个事务对该行数据进行了修改,并提交。
● 两次更新问题(Second lost updates problem):无法重复读取的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
● 虚读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
posted @
2009-07-19 21:04 jadmin 阅读(149) |
评论 (0) |
编辑 收藏
6.5.4 使用HibernateCallBack
HibernateTemplate还提供了一种更加灵活的方式来操作数据库,通过这种方式可以完全使用Hibernate的操作方式。HibernateTemplate的灵活访问方式可通过如下两个方法完成:
● Object execute(HibernateCallback action)。
● List execute(HibernateCallback action)。
这两个方法都需要一个HibernateCallback的实例,HibernateCallback实例可在任何有效的Hibernate数据访问中使用。程序开发者通过HibernateCallback,可以完全使用Hibernate灵活的方式来访问数据库,解决Spring封装Hibernate后灵活性不足的缺陷。
HibernateCallback是一个接口,该接口包含一个方法doInHibernate(org.hibernate. Session session),该方法只有一个参数Session。在开发中提供HibernateCallback实现类时,必须实现接口里包含的doInHibernate方法,在该方法体内即可获得Hibernate Session的引用,一旦获得了Hibernate Session的引用,就可以完全以Hibernate的方式进行数据库访问。
注意:doInHibernate方法内可以访问Session,该Session对象是绑定在该线程的Session实例。该方法内的持久层操作,与不使用Spring时的持久层操作完全相同。这保证了对于复杂的持久层访问,依然可以使用Hibernate的访问方式。
下面的代码对HibernateDaoSupport类进行扩展(虽然Spring 2.0的HibernateTemplate提供了一个分页方法setMaxResults,但仅此一个方法依然不能实现分页查询),这种扩展主要是为该类增加了3个分页查询的方法,分页查询时必须直接调用Hibernate的Session完成,因此,必须借助于HibernateCallBack的帮助。
public class YeekuHibernateDaoSupport extends HibernateDaoSupport
{
/**
* 使用hql 语句进行分页查询操作
* @param hql 需要查询的hql语句
* @param offset 第一条记录索引
* @param pageSize 每页需要显示的记录数
* @return 当前页的所有记录
*/
public List findByPage(final String hql,
final int offset, final int pageSize)
{
//HibernateDaoSupport已经包含了getHibernateTemplate()方法
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
//该方法体内以Hibernate方法进行持久层访问
{
List result = session.createQuery(hql)
.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
/**
* 使用hql 语句进行分页查询操作
* @param hql 需要查询的hql语句
* @param value 如果hql有一个参数需要传入,value就是传入的参数
* @param offset 第一条记录索引
* @param pageSize 每页需要显示的记录数
* @return 当前页的所有记录
*/
public List findByPage(final String hql , final Object value ,
final int offset, final int pageSize)
{
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
{
//下面查询的是最简单的Hiberante HQL查询
List result = session.createQuery(hql)
.setParameter(0, value)
.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
/**
* 使用hql 语句进行分页查询操作
* @param hql 需要查询的hql语句
* @param values 如果hql有多个参数需要传入,values就是传入的参数数组
* @param offset 第一条记录索引
* @param pageSize 每页需要显示的记录数
* @return 当前页的所有记录
*/
public List findByPage(final String hql, final Object[] values,
final int offset, final int pageSize)
{
List list = getHibernateTemplate().executeFind(new
HibernateCallback()
{
public Object doInHibernate(Session session)
throws HibernateException, SQLException
{
Query query = session.createQuery(hql);
for (int i = 0 ; i < values.length ; i++)
{
query.setParameter( i, values[i]);
}
List result = query.setFirstResult(offset)
.setMaxResults(pageSize)
.list();
return result;
}
});
return list;
}
}
在上面的代码实现中,直接使用了getHibernateTemplate()方法,这个方法由Hibernate- DaoSupport提供。而YeekuHibernateDaoSupport是HibernateDaoSupport的子类,因此,可以直接使用该方法。
当实现doInHibernate(Session session)方法时,完全以Hibernate的方式进行数据库访问,这样保证了Hibernate进行数据库访问的灵活性。
注意:Spring提供的XxxTemplate和XxxCallBack互为补充,二者体现了Spring框架设计的用心良苦:XxxTemplate对通用操作进行封装,而XxxCallBack解决了封装后灵活性不足的缺陷。
6.5.5 实现DAO组件
为了实现DAO组件,Spring提供了大量的XxxDaoSupport类,这些DAO支持类对于实现DAO组件大有帮助,因为这些DAO支持类已经完成了大量基础性工作。
Spring为Hibernate的DAO提供了工具类HibernateDaoSupport。该类主要提供如下两个方法以方便DAO的实现:
● public final HibernateTemplate getHibernateTemplate()。
● public final void setSessionFactory(SessionFactory sessionFactory)。
其中,setSessionFactory方法可用于接收Spring的ApplicationContext的依赖注入,可接收配置在Spring的SessionFactory实例,getHibernateTemplate方法用于返回通过SessionFactory产生的HibernateTemplate实例,持久层访问依然通过HibernateTemplate实例完成。
下面实现的DAO组件继承了Spring提供的HibernateDaoSupport类,依然实现了PersonDao接口,其功能与前面提供的PersonDao实现类完全相同。其代码如下:
public class PersonDaoHibernate extends HibernateDaoSupport implements PersonDao
{
/**
* 加载人实例
* @param id 需要加载的Person实例的主键值
* @return 返回加载的Person实例
*/
public Person get(int id)
{
return (Person)getHibernateTemplate().get(Person.class, new
Integer(id));
}
/**
* 保存人实例
* @param person 需要保存的Person实例
*/
public void save(Person person)
{
getHibernateTemplate().save(person);
}
/**
* 修改Person实例
* @param person 需要修改的Person实例
*/
public void update(Person person)
{
getHibernateTemplate().update(person);
}
/**
* 删除Person实例
* @param id 需要删除的Person的id
*/
public void delete(int id)
{
getHibernateTemplate().delete(getHibernateTemplate().
get(Person.class, new Integer(id)));
}
/**
* 删除Person实例
* @param person 需要删除的Person实例
*/
public void delete(Person person)
{
getHibernateTemplate().delete(person);
}
/**
* 根据用户名查找Person
* @param name 用户名
* @return 用户名对应的全部用户
*/
public List findByPerson(String name)
{
return getHibernateTemplate().find("from Person p where p.name
like ?" , name);
}
/**
* 返回全部的Person实例
* @return 全部的Person实例
*/
public List findAllPerson()
{
return getHibernateTemplate().find("from Person ");
}
}
上面的代码与前面的PersonDAOImpl对比会发现,代码量大大减少。事实上,DAO的实现依然借助于HibernateTemplate的模板访问方式,只是HibernateDaoSupport将依赖注入SessionFactory的工作已经完成,获取HibernateTemplate的工作也已完成。该DAO的配置必须依赖于SessionFactory,配置文件与前面部署DAO组件的方式完全相同,此处不再赘述。
在继承HibernateDaoSupport的DAO实现里,Hibernate Session的管理完全不需要打开代码,而由Spring来管理。Spring会根据实际的操作,采用“每次事务打开一次session”的策略,自动提高数据库访问的性能。
6.5.6 使用IoC容器组装各种组件
至此为止,J2EE应用所需要的各种组件都已经出现了,从MVC层的控制器组件,到业务逻辑组件,以及持久层的DAO组件,已经全部成功实现。应用程序代码并未将这些组件耦合在一起,代码中都是面向接口编程,因此必须利用Spring的IoC容器将他们组合在一起。
从用户角度来看,用户发出HTTP请求,当MVC框架的控制器组件拦截到用户请求时,将调用系统的业务逻辑组件,而业务逻辑组件则调用系统的DAO组件,而DAO组件则依赖于SessionFactory和DataSource等底层组件实现数据库访问。
从系统实现角度来看,IoC容器先创建SessionFactory和DataSource等底层组件,然后将这些底层组件注入给DAO组件,提供一个完整的DAO组件,并将此DAO组件注入给业务逻辑组件,从而提供一个完整的业务逻辑组件,而业务逻辑组件又被注入给控制器组件,控制器组件负责拦截用户请求,并将处理结果呈现给用户——这一系列的衔接都由Spring的IoC容器提供实现。
下面给出关于如何在容器中配置J2EE组件的大致模板,其模板代码如下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入数据源,注入的正是上文中定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出全部映射文件 -->
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
<!-- 此处还可列出更多的PO映射文件 -->
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的连接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 指定启动应用时,是否根据Hibernate映射文件创建数据表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Person持久化类的DAO Bean -->
<bean id="personDao" class="lee.PersonDaoImpl">
<!-- 采用依赖注入来传入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 下面能以相同的方式配置更多的持久化Bean -->
...
<bean id="myService" class="lee.MyServiceImp">
<!-- 注入业务逻辑组件所必需的DAO组件 -->
<property name="peronDdao" ref=" personDao "/>
<!-- 此处可采用依赖注入更多的DAO组件 -->
...
</bean>
<!-- 配置控制器Bean,设置起作用域为Request -->
<bean name="/login" class="lee.LoginAction" scope="request">
<!-- 依赖注入控制器所必需的业务逻辑组件 -->
<property name="myService" ref=" myService "/>
</bean>
</beans>
在上面的配置文件中,同时配置了控制器Bean、业务逻辑组件Bean、DAO组件Bean以及一些基础资源Bean。各组件的组织被解耦到配置文件中,而不是在代码层次的低级耦合。
当客户端的HTTP请求向/login.do发送请求时,将被容器中的lee.LoginAction拦截,LoginAction调用myService Bean,myService Bean则调用personDao等系列DAO组件,整个流程将系统中的各组件有机地组织在一起。
注意:在实际应用中,很少会将DAO组件、业务逻辑组件以及控制组件都配置在同一个文件中。而是在不同配置文件中,配置相同一组J2EE应用组件。
6.5.7 使用声明式事务
在上面的配置文件中,部署了控制器组件、业务逻辑组件、DAO组件,几乎可以形成一个完整的J2EE应用。但有一个小小的问题:事务控制。系统没有任何事务逻辑,没有事务逻辑的应用是不可想象的。
Spring提供了非常简洁的声明式事务控制,只需要在配置文件中增加事务控制片段,业务逻辑代码无须任何改变。Spring的声明式事务逻辑,甚至支持在不同事务策略之间切换。
配置Spring声明式事务时,通常推荐使用BeanNameAutoProxyCreator自动创建事务代理。通过这种自动事务代理的配置策略,增加业务逻辑组件,只需要在BeanNameAutoProxyCreator Bean配置中增加一行即可,从而避免了增量式配置。
在上面的配置模板文件中增加如下配置片段,系统的myService业务逻辑组件将变成事务代理Bean,从而为业务逻辑方法增加事务逻辑。
<!-- 配置Hibernate的局部事务管理器 -->
<!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager
接口,针对采用Hibernate持久化连接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean需要依赖注入一个SessionFactory
bean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置事务拦截器Bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事务拦截器bean需要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对满足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是所有需要自动创建事务代理的Bean -->
<list>
<value>myService</value>
<!-- 下面还可增加需要增加事务逻辑的业务逻辑Bean -->
...
</list>
<!-- 此处可增加其他需要自动创建事务代理的Bean -->
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的Interceptor -->
</list>
</property>
</bean>
一旦增加了如上的配置片段,系统中的业务逻辑方法就有了事务逻辑。这种声明式事务配置方式可以在不同的事务策略之间自由切换。
提示:尽量使用声明式事务配置方式,而不要在代码中完成事务逻辑。
posted @
2009-07-19 10:24 jadmin 阅读(375) |
评论 (0) |
编辑 收藏
6.5 Spring整合Hibernate
时至今日,可能极少有J2EE应用会直接以JDBC方式进行持久层访问。毕竟,用面向对象的程序设计语言来访问关系型数据库,是一件让人沮丧的事情。大部分时候,J2EE应用都会以ORM框架来进行持久层访问,在所有的ORM框架中,Hibernate以其灵巧、轻便的封装赢得了众多开发者的青睐。
Spring具有良好的开放性,能与大部分ORM框架良好整合。下面将详细介绍Spring与Hibernate的整合。
6.5.1 Spring提供的DAO支持
DAO模式是一种标准的J2EE设计模式,DAO模式的核心思想是,所有的数据库访 问,都通过DAO组件完成,DAO组件封装了数据库的增、删、改等原子操作。而业务逻辑组件则依赖于DAO组件提供的数据库原子操作,完成系统业务逻辑的实现。
对于J2EE应用的架构,有非常多的选择,但不管细节如何变换,J2EE应用都大致可分为如下3层:
● 表现层。
● 业务逻辑层。
● 数据持久层。
轻量级J2EE架构以Spring IoC容器为核心,承上启下。其向上管理来自表现层的Action,向下管理业务逻辑层组件,同时负责管理业务逻辑层所需的DAO对象。各层之间负责传值的是值对象,也就是JavaBean实例。
图6.5精确地描绘了轻量级J2EE架构的大致情形。
DAO组件是整个J2EE应用的持久层访问的重要组件,每个J2EE应用的底层实现都难以离开DAO组件的支持。Spring对实现DAO组件提供了许多工具类,系统的DAO组件可通过继承这些工具类完成,从而可以更加简便地实现DAO组件。
Spring的DAO支持,允许使用相同的方式、不同的数据访问技术,如JDBC、Hibernate或JDO。Spring的DAO在不同的持久层访问技术上提供抽象,应用的持久层访问基于Spring的DAO抽象。因此,应用程序可以在不同的持久层技术之间切换。
Spring提供了一系列的抽象类,这些抽象将被作为应用中DAO实现类的父类。通过继承这些抽象类,Spring简化了DAO的开发步骤,能以一致的方式使用数据库访问技术。不管底层采用JDBC、JDO或Hibernate,应用中都可采用一致的编程模型。
图6.5 轻量级J2EE应用架构
应用的DAO类继承这些抽象类,会大大简化应用的开发。最大的好处是,继承这些抽象类的DAO能以一致的方式访问数据库,意味着应用程序可以在不同的持久层访问技术中切换。
除此之外,Spring提供了一致的异常抽象,将原有的Checked异常转换包装成Runtime异常,因而,编码时无须捕获各种技术中特定的异常。Spring DAO体系中的异常,都继承DataAccessException,而DataAccessException异常是Runtime的,无须显式捕捉。通过DataAccessException的子类包装原始异常信息,从而保证应用程序依然可以捕捉到原始异常信息。
Spring提供了多种数据库访问技术的DAO支持,包括Hibernate、JDO、TopLink、iBatis、OJB等。Spring可以使用相同的访问模式、不同的数据库访问技术。就Hibernate的持久层访问技术而言,Spring提供了如下3个工具类(或接口)来支持DAO组件的实现:
● HibernateDaoSupport。
● HibernateTemplate。
● HibernateCallBack。
6.5.2 管理Hibernate的SessionFactory
前面介绍Hibernate时已经知道,在通过Hibernate进行持久层访问时,Hibernate的SessionFactory是一个非常重要的对象,它是单个数据库映射关系编译后的内存镜像。大部分情况下,一个J2EE应用对应一个数据库,也即对应一个SessionFactory对象。
在纯粹的Hibernate访问中,应用程序需要手动创建SessionFactory实例,可想而知,这不是一个优秀的策略。在实际开发中,希望以一种声明式的方式管理SessionFactory实例,直接以配置文件来管理SessionFactory实例,在示范Struts的PlugIn扩展点时,大致示范了这种方式(请参阅2.12.1节的内容)。
Spring的IoC容器则提供了更好的管理方式,它不仅能以声明式的方式配置Session- Factory实例,也可充分利用IoC容器的作用,为SessionFactory注入数据源引用。
下面是Spring配置文件中配置Hibernate SessionFactory的示范代码:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0. ComboPooledDataSource"
destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入数据源,正是上文定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出全部映射文件 -->
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的连接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 配置启动应用时,是否根据Hibernate映射自动创建数据表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
</beans>
一旦在Spring的IoC容器中配置了SessionFactory Bean,它将随应用的启动而加载,并可以充分利用IoC容器的功能,将SessionFactory Bean注入任何Bean,比如DAO组件。一旦DAO组件获得了SessionFactory Bean的引用,就可以完成实际的数据库访问。
当然,Spring也支持访问容器数据源。如果需要使用容器数据源,可将数据源Bean修改成如下配置:
<!-- 此处配置JNDI数据源 -->
<bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<!-- 指定数据源的JNDI -->
<value>java:comp/env/jdbc/myds</value>
</property>
</bean>
可见,以声明式的方式管理SessionFactory实例,可以让应用在不同数据源之间切换。如果应用更换数据库等持久层资源,只需对配置文件进行简单修改即可。
提示:以声明式的方式管理SessionFactory,非常类似于早期将数据库服务的相关信息放在web.xml文件中进行配置。这种方式是为了提供更好的适应性,当持久层服务需要更改时,应用代码无须任何改变。
6.5.3 使用HibernateTemplate
HibernateTemplate提供持久层访问模板,使用HibernateTemplate无须实现特定接口,它只需要提供一个SessionFactory的引用就可执行持久化操作。SessionFactory对象既可通过构造参数传入,也可通过设值方式传入。HibernateTemplate提供如下3个构造函数:
● HibernateTemplate()。
● HibernateTemplate(org.hibernate.SessionFactory sessionFactory)。
● HibernateTemplate(org.hibernate.SessionFactory sessionFactory, boolean allowCreate)。
第一个构造函数,构造一个默认的HibernateTemplate实例。因此,使用Hibernate- Template实例之前,还必须使用方法setSessionFactory(SessionFactory sessionFactory)来为HibernateTemplate传入SessionFactory的引用。
第二个构造函数,在构造时已经传入SessionFactory引用。
第三个构造函数,其boolean型参数表明,如果当前线程已经存在一个非事务性的Session,是否直接返回此非事务性的Session。
在Web应用中,通常启动时自动加载ApplicationContext,SessionFactory和DAO对象都处在Spring上下文管理下,因此无须在代码中显式设置,可采用依赖注入完成Session- Factory和DAO的解耦,依赖关系通过配置文件来设置,如下所示:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"
destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory Bean -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入数据源,注入的正是上文中定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出全部映射文件 -->
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的连接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 指定启动应用时,是否根据Hibernate映射文件创建数据表 -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Person持久化类的DAO bean -->
<bean id="personDao" class="lee.PersonDaoImpl">
<!-- 采用依赖注入来传入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
在PersonDao组件中,所有的持久化操作都通过HibernateTemplate实例完成,而HibernateTemplate操作数据库非常简洁,大部分CRUD操作都可通过一行代码解决问题。下面介绍如何通过HibernateTemplate进行持久层访问。
HibernateTemplate提供了非常多的常用方法来完成基本的操作,比如通常的增加、删除、修改、查询等操作,Spring 2.0更增加了对命名SQL查询的支持,也增加了对分页的支持。大部分情况下,使用Hibernate的常规用法,就可完成大多数DAO对象的CRUD操作。下面是HibernateTemplate的常用方法简介:
● void delete(Object entity),删除指定持久化实例。
● deleteAll(Collection entities),删除集合内全部持久化类实例。
● find(String queryString),根据HQL查询字符串来返回实例集合。
● findByNamedQuery(String queryName),根据命名查询返回实例集合。
● get(Class entityClass, Serializable id),根据主键加载特定持久化类的实例。
● save(Object entity),保存新的实例。
● saveOrUpdate(Object entity),根据实例状态,选择保存或者更新。
● update(Object entity),更新实例的状态,要求entity是持久状态。
● setMaxResults(int maxResults),设置分页的大小。
下面是一个完整DAO类的源代码:
public class PersonDaoImpl implements PersonDao
{
//执行持久化操作的HibernateTemplate实例
private HibernateTemplate ht = null;
private SessionFactory sessionFactory;
//该DAO组件持久化操作所需的SessionFactory对象
public void setSessionFactory(SessionFactory sessionFactory)
{
this.sessionFactory = sessionFactory;
}
//用于根据SessionFactory实例返回HibernateTemplate实例的方法
private HibernateTemplate getHibernateTemplate()
{
if (ht == null)
{
ht = new HibernateTemplate(sessionFactory);
}
return ht;
}
/**
* 加载人实例
* @param id 需要加载的Person实例的主键值
* @return 返回加载的Person实例
*/
public Person get(int id)
{
return (Person)getHibernateTemplate().get(Person.class, new
Integer(id));
}
/**
* 保存人实例
* @param person 需要保存的Person实例
*/
public void save(Person person)
{
getHibernateTemplate().save(person);
}
/**
* 修改Person实例
* @param person 需要修改的Person实例
*/
public void update(Person person)
{
getHibernateTemplate().update(person);
}
/**
* 删除Person实例
* @param id 需要删除的Person的id
*/
public void delete(int id)
{
getHibernateTemplate().delete(getHibernateTemplate().get(Person.
class,new Integer(id)));
}
/**
* 删除Person实例
* @param person 需要删除的Person实例
*/
public void delete(Person person)
{
getHibernateTemplate().delete(person);
}
/**
* 根据用户名查找Person
* @param name 用户名
* @return 用户名对应的全部用户
*/
public List findByName(String name)
{
return getHibernateTemplate().find("from Person p where p.name
like ?" , name);
}
/**
* 返回全部的Person实例
* @return 全部的Person实例
*/
public List findAllPerson()
{
return getHibernateTemplate().find("from Person ");
}
}
通过上面实现DAO组件的代码可以看出,通过HibernateTemplate进行持久层访问的代码如此清晰,大部分CRUD操作一行代码即可完成,完全无须Hibernate访问那些繁琐的步骤。而且,一旦DAO组件获得了SessionFactory的引用,即可很轻易地创建HibernateTemplate实例。
提示:HibernateTemplate是Spring众多模板工具类之一,Spring正是通过这种简便地封装,完成了开发中大量需要重复执行的工作。
posted @
2009-07-19 10:24 jadmin 阅读(671) |
评论 (0) |
编辑 收藏
Struts的plug-in配置部分明确指出,Spring的配置文件有两个:applicationContext.xml和action-servlet.xml。其实,完全可以使用一个配置文件。通常,习惯将Action Bean配置在控制器的context内。action-servlet.xml用于配置表现层上下文,其详细配置信息如下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring配置文件的根元素,以及对应的Schame信息 -->
<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.xsd">
<!-- 每个request请求产生一个新实例,将所有该请求的作用域配置成request -->
<bean name="/login" class="lee.LoginAction" scope="request">
<property name="vb" ref="vb"/>
</bean>
</beans>
因为每次请求都应该启动新的Action处理用户请求,因此,应将Action的作用域配置成Request。
注意:ActionServlet转发请求时,是根据Bean的name属性,而不是id属性。因此,此处确定的name属性与Struts的action属性相同。
applicationContext.xml只有一个bean配置,即vb bean。其详细配置如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring 配置文件的根元素,以及对应的Schema信息 -->
<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.xsd">
<!-- 配置ValidBean实例 -->
<bean id="vb" class="lee.ValidBeanImpl"/>
</beans>
ValidBeanImpl是一个业务逻辑bean,本示例程序中仅作简单的判断,ValidBeanImpl的源代码如下:
//面向接口编程,实现ValidBean接口
public class ValidBeanImpl implements ValidBean
{
//根据输入的用户名和密码判断是否有效
public boolean valid(String username,String pass)
{
//有效,返回true
if (username.equals("scott") && pass.equals("tiger"))
{
return true;
}
return false;
}
}
注意:上面的业务逻辑组件非常简单,它只是一个示意。如果是真实的应用,业务逻辑组件应该通过DAO组件来实现业务逻辑方法。
应用的业务逻辑控制器,Action则负责调用业务逻辑组件的方法,并根据业务逻辑组件方法的返回值,确定如何响应用户请求。下面是该示例应用控制器的代码:
//业务控制器继承Action
public class LoginAction extends Action
{
//action控制器将调用的业务逻辑组件
private ValidBean vb;
//依赖注入业务逻辑组件的setter方法
public void setVb(ValidBean vb)
{
this.vb = vb;
}
//必须重写该核心方法,该方法actionForm将表单的请求参数封装成值对象
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//form由ActionServlet转发请求时创建,封装了所有的请求参数
LoginForm loginForm = (LoginForm)form;
//获取username请求参数
String username = loginForm.getUsername();
//获取pass请求参数
String pass = loginForm.getPass();
//下面为服务器端的数据校验
String errMsg = "";
//判断用户名不能为空
if (username == null || username.equals(""))
{
errMsg += "您的用户名丢失或没有输入,请重新输入";
}
//判断密码不能为空
else if(pass == null || pass.equals(""))
{
errMsg += "您的密码丢失或没有输入,请重新输入";
}
//如果用户名和密码不为空,才调用业务逻辑组件
else
{
//vb是业务逻辑组件,由容器注入
if (vb.valid(username,pass))
{
return mapping.findForward("welcome");
}
else
{
errMsg = "您的用户名和密码不匹配";
}
}
//判断是否生成了错误信息
if (errMsg != null && !errMsg.equals(""))
{
//如果有错误信息,将错误信息保存在request里,并跳转到input对应的
forward对象
request.setAttribute("err" , errMsg);
return mapping.findForward("input");
}
else
{
//如果没有错误信息,跳转到welcome对应的forward对象
return mapping.findForward("welcome");
}
}
}
在本应用中,使用了Struts的客户端数据校验,让Action继承ValidatorActionForm即可。ActionForm的代码非常简单,此处不再赘述。
为了完成数据校验,还应该编写数据校验规则文件。在struts-config.xml文件的尾部,另有一个plug-in用来加载校验文件,其中validator-rules.xml文件位于struts压缩包的lib下,直接复制过来即可使用,而validator.xml必须自己编写,validator.xml文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 验证规则文件的文件头,包括DTD等信息 -->
<!DOCTYPE form-validation PUBLIC
"-//Apache Software Foundation//DTD Commons Validator Rules
Configuration 1.1.3//EN"
"http://jakarta.apache.org/commons/dtds/validator_1_1_3.dtd">
<!-- 验证文件的根元素 -->
<form-validation>
<!-- 所有需要验证的form都放在formset里 -->
<formset>
<!-- 需要验证的form名,该名与struts里配置的名相同 -->
<form name="loginForm">
<!-- 指定该form的username域必须满足的规则:必填、模式匹配 -->
<field property="username" depends="required,mask">
<arg key="loginForm.username" position="0"/>
<var>
<!-- 确定匹配模式的正则表达式 -->
<var-name>mask</var-name>
<var-value>^[a-zA-Z]+$</var-value>
</var>
</field>
<!-- 指定该form的pass域必须满足的规则:必填 -->
<field property="pass" depends="required">
<msg name="required" key="pass.required"/>
<arg key="loginForm.pass" position="0"/>
</field>
</form>
</formset>
</form-validation>
上面示例程序的结构非常清晰:表现层组件(Action)配置在action-servlet.xml文件中,而业务逻辑层组件(vb)配置在applicationContext.xml文件中,如果应用中有DAO组件,将DAO组件配置在dao-context.xml文件中。将3个文件放在plug-in元素里一起加载。
DelegatingRequestProcessor会将请求转发到Action,该Action已经处于IoC容器管理之下,因此,可以方便地访问容器中的其他Bean。
通过配置文件可以看出,Action根本无须type属性,即struts-config.xml中Action根本没有实例化过,DelegatingRequestProcessor将请求转发给Spring容器中的同名Bean。这种转发的时机非常早,避免了创建struts-config.xml配置文件中的Action,因而性能非常好。
图6.3是采用这种整合策略的执行效果。
6.4.4 使用DelegatingActionProxy
使用DelegatingRequestProcessor简单方便,但有一个缺点,RequestProcessor是Struts的一个扩展点,也许应用程序本身就需要扩展RequestProcessor,而DelegatingRequest- Processor已经使用了这个扩展点。
为了重新利用Struts的RequestProcessor这个扩展点,有两个做法:
● 应用程序的RequestProcessor不再继承Struts的RequestProcessor,改为继承DelegatingRequestProcessor。
● 使用DelegatingActionProxy。
前者常常有一些未知的风险,而后者是Spring推荐的整合策略。使用Delegating- ActionProxy与DelegatingRequestProcessor的目的都只有一个,将请求转发给Spring管理的Bean。
DelegatingRequestProcessor直接替换了原有的RequestProcessor,在请求转发给action之前,转发给Spring管理的Bean;而DelegatingActionProxy则被配置成Struts的Action,即所有的请求先被ActionServlet截获,请求被转发到对应的Action,而action的实现类全都是DelegatingActionProxy,DelegatingActionProxy再将请求转发给Spring容器的Action。
可以看出,使用DelegatingActionProxy比使用DelegatingRequestProcessor要晚一步转发到Spring的context。但通过这种方式可以避免占用扩展点。
与使用DelegatingRequestProcessor相比,使用DelegatingActionProxy仅需要去掉controller配置元素,并将所有的action实现类改为DelegatingActionProxy即可。详细的配置文件如下:
<!-- XML文件的版本和编码集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- struts配置文件的文件头,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->
<form-beans>
<!-- 定义了一个formbean,确定formbean名和实现类 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->
<action-mappings>
<!-- 这里只定义了一个action。必须配置action的type元素为
DelegatingActionProxy -->
<action path="/login" type="org.springframework.web.struts.
DelegatingActionProxy"
name="loginForm" scope="request" validate="true" input=
"/login.jsp" >
<!-- 定义action内的两个局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 加载国际化的资源包 -->
<message-resources parameter="mess"/>
<!-- 装载验证的资源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
<!-- 装载Spring配置文件,随应用启动创建ApplicationContext实例 -->
<plug-in className="org.springframework.web.struts. ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>
DelegatingActionProxy接收ActionServlet转发过来的请求,然后转发给Application- Context管理的Bean,这是典型的链式处理。
通过配置文件可以看出,struts-config.xml文件中配置了大量DelegatingActionProxy实例,Spring容器中也配置了同名的Action。即Struts的业务控制器分成了两个部分:第一个部分是Spring的DelegatingActionProxy,这个部分没有实际意义,仅仅完成转发;第二个部分是用户的Action实现类,该实现类负责真实的处理。
这种策略的性能比前一种策略的效果要差一些,因为需要多创建一个Delegating- ActionProxy实例。而且,J2EE应用中Action非常多,这将导致大量创建DelegatingActionProxy实例,使用一次之后,等待垃圾回收机制回收——这对性能的影响不可避免。
图6.4是DelegatingActionProxy的执行效果。
注意:使用DelegatingActionProxy的整合策略,可避免占用Struts的RequestProcessor扩展点,但降低了整合性能。
6.4.5 使用ActionSupport代替Action
前面已经介绍了,Spring与Struts的整合还有一种策略,让Struts的Action显式获取Spring容器中的Bean。在这种策略下,Struts的Action不接受IoC容器管理,Action的代码与Spring API部分耦合,造成代码污染。这种策略也有其好处:代码的可读性非常强,Action的代码中显式调用业务逻辑组件,而无须等待容器注入。
Action中访问ApplicationContext有两种方法:
● 利用WebApplicationContextUtils工具类。
● 利用ActionSupport支持类。
通过WebApplicationContextUtils,可以显式获得Spring容器的引用(请参阅6.4.1节的内容),而ActionSupport类则提供了一个更简单的方法getWebApplicationContext(),该方法可直接获取Spring容器的引用。
所谓ActionSupport类,是指Spring提供了系列扩展。Spring扩展了Struts的Action,在Struts的Action后加上Support后缀,Spring扩展的Action有如下几个:
● ActionSupport。
● DispatchActionSupport。
● LookupDispatchActionSupport。
● MappingDispatchActionSupport。
下面的示例将展示这种整合策略,在这种整合策略下,Struts的Action改为继承Spring扩展后的Action,下面是应用的Action代码:
//新的业务控制器,继承Spring的ActionSupport类
public class LoginAction extends ActionSupport
{
//依然将ValidBean作为成员变量
private ValidBean vb;
//构造器,注意:不可在构造器中调用getWebApplicationContext()方法
public LoginAction()
{
}
//完成ValidBean的初始化
public ValidBean getVb()
{
return(ValidBean)getWebApplicationContext().getBean("vb");
}
//必须重写该核心方法,该方法actionForm将表单的请求参数封装成值对象
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response)throws
Exception
{
//form由ActionServlet转发请求时创建,封装了所有的请求参数
LoginForm loginForm = (LoginForm)form;
//获取username请求参数
String username = loginForm.getUsername();
//获取pass请求参数
String pass = loginForm.getPass();
//下面为服务器端的数据校验
String errMsg = "";
//判断用户名不能为空
if (username == null || username.equals(""))
{
errMsg += "您的用户名丢失或没有输入,请重新输入";
}
//判断密码不能为空
else if(pass == null || pass.equals(""))
{
errMsg += "您的密码丢失或没有输入,请重新输入";
}
//如果用户名和密码不为空,才调用业务逻辑组件
else
{
//vb是业务逻辑组件,通过上面的初始化方法获得
if (getVb().valid(username,pass))
{
return mapping.findForward("welcome");
}
else
{
errMsg = "您的用户名和密码不匹配";
}
}
//判断是否生成了错误信息
if (errMsg != null && !errMsg.equals(""))
{
//如果有错误信息,将错误信息保存在request里,并跳转到input对应的
//forward对象
request.setAttribute("err" , errMsg);
return mapping.findForward("input");
}
else
{
//如果没有错误信息,跳转到welcome对应的forward对象
return mapping.findForward("welcome");
}
}
}
在上面的Action代码中,Action显式获取容器中的业务逻辑组件,而不是依靠Spring容器的依赖注入。在这种整合策略下,表现层的控制器组件不再接受IoC容器管理。因此,没有控制器上下文,应将原有的action-servlet.xml文件删除,并修改plug-in元素,不要加载该文件。还要修改Action配置,将Action配置的type元素修改成实际的处理类。这 种整合策略也有一个好处:代码的可读性更强,对传统Struts应用开发的改变很小,容易使用。
将该Action部署在struts-config.xml中,Struts将负责创建该Action。struts-config.xml文件的源代码如下:
<!-- XML文件的版本和编码集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- Struts配置文件的文件头,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->
<form-beans>
<!-- 定义了一个formbean,确定formbean名和实现类 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->
<action-mappings>
<!-- 这里只定义了一个action。action的类型为ActionSupport的子类 -->
<action path="/login" type="type="lee.LoginAction"
name="loginForm" scope="request" validate="true" input=
"/login.jsp" >
<!-- 定义action内的两个局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 加载国际化的资源包 -->
<message-resources parameter="mess"/>
<!-- 装载验证的资源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
</struts-config>
此时,Spring无须使用配置Action的配置文件,这种配置方式非常简单。只需要业务逻辑组件的配置文件,业务逻辑组件的配置文件与前面的示例没有任何改变。
该配置文件中的业务逻辑组件由Spring容器负责实现,而ActionSupport能够先定位Spring容器,然后获得容器的业务逻辑组件。
这种整合策略的执行效果与前面两种整合策略的执行效果完全相同。从代码中分析可见,在这种整合策略下,业务控制器再次退回到Struts起初的设计。仅由strutsconfig.xml中Action充当,从而避免了像DelegatingActionProxy整合策略的性能低下,因为可以只需要创建实际的Action实例。
注意:在这种整合策略下,Struts开发者的改变最小,最接近传统Struts应用开发者的习惯。但这种整合策略会造成代码污染,因为Action类必须继承Spring的ActionSupport类。
posted @
2009-07-19 10:23 jadmin 阅读(72) |
评论 (0) |
编辑 收藏
6.4 Spring整合Struts
虽然Spring也提供了自己的MVC组件,但一来Spring的MVC组件过于繁琐,二 来Struts的拥护者实在太多。因此,很多项目都会选择使用Spring整合Struts框架。而且Spring确实可以无缝整合Struts框架,二者结合成一个更实际的J2EE开发平台。
6.4.1 利用Struts的PlugIn来启动Spring容器
使用Spring的Web应用时,不用手动创建Spring容器,而是通过配置文件声明式地创建Spring容器。因此,在Web应用中创建Spring容器有如下两个方式:
● 直接在web.xml文件中配置创建Spring容器。
● 利用第三方MVC框架的扩展点,创建Spring容器。
其实第一种创建Spring容器的方式更加常见。为了让Spring容器随Web应用的启动而自动启动,有如下两个方法:
● 利用ServletContextListener实现。
● 采用load-on-startup Servlet实现。
Spring提供ServletContextListener的一个实现类ContextLoaderListener,该类可以作为Listener使用,会在创建时自动查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,只需在web.xml文件中增加如下配置片段即可:
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
如果有多个配置文件需要载入,则考虑使用<context-param>元素来确定配置文件的文件名。ContextLoaderListener加载时,会查找名为contextConfigLocation的参数。因此,配置context-param时,参数名字应该是contextConfigLocation。
带多个配置文件的web.xml文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及相应的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<!-- 确定多个配置文件 -->
<context-param>
<!-- 参数名为contextConfigLocation -->
<param-name>contextConfigLocation</param-name>
<!-- 多个配置文件之间以“,”隔开 -->
<param-value>/WEB-INF/daoContext.xml,/WEB-INF/
applicationContext.xml</param-value>
</context-param>
<!-- 采用listener创建ApplicationContext实例 -->
<listener>
<listener-class>org.springframework.web.context.
ContextLoaderListener</listener-class>
</listener>
</web-app>
如果没有通过contextConfigLocation指定配置文件,Spring会自动查找application- Context.xml配置文件;如果有contextConfigLocation,则利用该参数确定的配置文件。如果无法找到合适的配置文件,Spring将无法正常初始化。
Spring根据bean定义创建WebApplicationContext对象,并将其保存在web应用的ServletContext中。大部分情况下,应用中的Bean无须感受到ApplicationContext的存在,只要利用ApplicationContext的IoC即可。
如果需要在应用中获取ApplicationContext实例,可以通过如下代码获取:
//获取当前Web应用的Spring容器
WebApplicationContext ctx =
WebApplicationContextUtils.getWebApplicationContext(servletContext);
除此之外,Spring提供了一个特殊的Servlet类ContextLoaderServlet。该Servlet在启动时,会自动查找WEB-INF/下的applicationContext.xml文件。
当然,为了让ContextLoaderServlet随应用的启动而启动,应将此Servlet配置成load-on-startup的Servlet,load-on-startup的值小一点比较合适,这样可以保证Application- Context更快的初始化。
如果只有一个配置文件,并且文件名为applicationContext.xml,在web.xml文件中增加如下一段即可:
<servlet>
<servlet-name>context</servlet-name>
<servlet-class>org.springframework.web.context.ContextLoaderServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
该Servlet用于提供“后台”服务,主要用于创建Spring容器,无须响应客户请求,因此无须配置servlet-mapping。
如果有多个配置文件,一样使用<context-param>元素来确定多个配置文件。
事实上,不管是ContextLoaderServlet,还是ContextLoaderListener,都依赖于ContextLoader创建ApplicationContext实例。
在ContextLoader代码的第240行,有如下代码:
String configLocation = servletContext.getInitParameter
(CONFIG_LOCATION_PARAM);
if (configLocation != null) {
wac.setConfigLocations(StringUtils.tokenizeToStringArray
(configLocation,
ConfigurableWebApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
其中,CONFIG_LOCATION_PARAM是该类的常量,其值为contextConfigLocation。可以看出,ContextLoader首先检查servletContext中是否有contextConfigLocation的参数,如果有该参数,则加载该参数指定的配置文件。
ContextLoaderServlet与ContextLoaderListener底层都依赖于ContextLoader。因此,二者的效果几乎没有区别。之间的区别不是它们本身引起的,而是由于Servlet规范,Listener比Servlet优先加载。因此,采用ContextLoaderListener创建ApplicationContext的时机更早。
当然,也可以通过ServletContext的getAttribute方法获取ApplicationContext。但使用WebApplicationContextUtils类更便捷,因为无须记住ApplicationContext的属性名。即使ServletContext的WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRI- BUTE属性没有对应对象,WebApplicationContextUtils的getWebApplicationContext()方法将会返回空,而不会引起异常。
到底需要使用Listener,还是使用load-on-startup Servlet来创建Spring容器呢?通常推荐使用Listener来创建Spring容器。但Listerner是Servlet 2.3以上才支持的标准,因此,必须Web容器支持Listener才可使用Listerner。
注意:使用Listener创建Spring容器之前,应先评估Web容器是否支持Listener标准。
还有一种情况,利用第三方MVC框架的扩展点来创建Spring容器,比如Struts。在第2章介绍Strust框架时,知道Struts有一个扩展点PlugIn。
实际上,Spring正是利用了PlugIn这个扩展点,从而提供与Struts的整合。Spring提供了PlugIn接口的实现类org.springframework.web.struts.ContextLoaderPlugIn。这个实现类可作为Struts的PlugIn配置,Struts框架启动时,将自动创建Spring容器。
为了利用Struts的PlugIn创建Spring容器,只需在Struts配置文件中增加如下片段 即可:
<plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/action-servlet.xml,/WEB-INF/applicationContext.
xml"/>
</plug-in>
其中,指定contextConfigLocation属性值时,即可以指定一个Spring配置文件的位置,可以指定多个Spring配置文件的位置。
6.4.2 MVC框架与Spring整合的思考
对于一个基于B/S架构的J2EE应用而言,用户请求总是向MVC框架的控制器请求,而当控制器拦截到用户请求后,必须调用业务逻辑组件来处理用户请求。此时有一个问题,控制器应该如何获得业务逻辑组件?
最容易想到的策略是,直接通过new关键字创建业务逻辑组件,然后调用业务逻辑组件的方法,根据业务逻辑方法的返回值确定结果。
实际的应用中,很少见到采用上面的访问策略,因为这是一种非常差的策略。不这样做至少有如下3个原因:
● 控制器直接创建业务逻辑组件,导致控制器和业务逻辑组件的耦合降低到代码层次,不利于高层次解耦。
● 控制器不应该负责业务逻辑组件的创建,控制器只是业务逻辑组件的使用者。无须关心业务逻辑组件的实现。
● 每次创建新的业务逻辑组件将导致性能下降。
答案是采用工厂模式或服务定位器。采用服务定位器的模式,是远程访问的场景。在这种场景下,业务逻辑组件已经在某个容器中运行,并对外提供某种服务。控制器无须理会该业务逻辑组件的创建,直接调用即可,但在调用之前,必须先找到该服务——这就是服务定位器的概念。经典J2EE应用就是这种结构的应用。
对于轻量级的J2EE应用,工厂模式则是更实际的策略。因为轻量级的J2EE应用里,业务逻辑组件不是EJB,通常就是一个POJO,业务逻辑组件的生成通常由工厂负责,而且工厂可以保证该组件的实例只需一个就够了,可以避免重复实例化造成的系统开销。
如图6.2就是采用工厂模式的顺序图。
图6.2 工厂模式顺序图
采用工厂模式,将控制器与业务逻辑组件的实现分离,从而提供更好的解耦。
在采用工厂模式的访问策略中,所有的业务逻辑组件的创建由工厂负责,业务逻辑组件的运行也由工厂负责。而控制器只需定位工厂实例即可。
如果系统采用Spring框架,则Spring成为最大的工厂。Spring负责业务逻辑组件的创建和生成,并可管理业务逻辑组件的生命周期。可以如此理解,Spring是一个性能非常优秀的工厂,可以生产出所有的实例,从业务逻辑组件,到持久层组件,甚至控制器。
现在的问题是,控制器如何访问到Spring容器中的业务逻辑组件?为了让Action访 问Spring的业务逻辑组件,有两种策略:
● Spring管理控制器,并利用依赖注入为控制器注入业务逻辑组件。
● 控制器显式定位Spring工厂,也就是Spring的容器ApplicationContext实例,并从工厂中获取业务逻辑组件实例的引用。
第一种策略,充分利用Spring的IoC特性,是最优秀的解耦策略。但不可避免带来一些不足之处,归纳起来主要有如下不足之处:
● Spring管理Action,必须将所有的Action配置在Spring容器中,而struts-config.xml文件中的配置也不会减少,导致配置文件大量增加。
● Action的业务逻辑组件接收容器注入,将导致代码的可读性降低。
总体而言,这种整合策略是利大于弊。
第二种策略,与前面介绍的工厂模式并没有太大的不同。区别是Spring容器充当了业务逻辑组件的工厂。控制器负责定位Spring容器,通常Spring容器访问容器中的业务逻辑组件。这种策略是一种折衷,降低了解耦,但提高了程序的可读性。
Spring完全支持这两种策略,既可以让Spring容器管理控制器,也可以让控制器显式定位Spring容器中的业务逻辑组件。
6.4.3 使用DelegatingRequestProcessor
这里介绍的是第一种整合策略:让Spring管理Struts的Action。那么同样有一个问题,让Spring管理Struts的Action时,客户端的HTTP 请求如何转向Spring容器中的Action?
当使用Struts作为MVC框架时,客户端的HTTP请求都是直接向ActionServlet请求的,因此关键就是让ActionServlet将请求转发给Spring容器中的Action。这很明显可以利用Spring的另一个扩展点:通过扩展RequestProcessor完成,使用扩展的RequestProcessor替换Struts的RequestProcessor。
Spring完成了这种扩展,Spring提供的DelegatingRequestProcessor继承Request- Processor。为了让Struts使用DelegatingRequestProcessor,还需要在struts-config.xml文件中增加如下一行:
//使用spring的RequestProcessor替换struts原有的RequestProcessor
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
完成这个设置后,Struts会将截获到的用户请求转发到Spring context下的bean,根据bean的name属性来匹配。而Struts中的action配置则无须配置class属性,即使配置了class属性也没有任何用处,即下面两行配置是完全一样的:
//配置struts action时,指定了实现类
<action path="/user" type="lee.UserAction"/>
//配置struts action时,没有指定实现类
<action path="/user"/>
下面的示例程序在上一个示例程序的基础上稍作修改,增加了客户端验证和程序国际化部分。也调用了Spring的业务bean来验证登录。先看修改后的struts-config.xml文件:
<!-- XML文件版本,编码集 -->
<?xml version="1.0" encoding="gb2312"?>
<!-- Struts配置文件的文件头,包括DTD等信息 -->
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.2//EN"
"http://struts.apache.org/dtds/struts-config_1_2.dtd">
<!-- struts配置文件的根元素 -->
<struts-config>
<!-- 配置formbean,所有的formbean都放在form-beans元素里定义 -->
<form-beans>
<!-- 定义了一个formbean,确定formbean名和实现类 -->
<form-bean name="loginForm" type="lee.LoginForm"/>
</form-beans>
<!-- 定义action部分,所有的action都放在action-mapping元素里定义 -->
<action-mappings>
<!-- 这里只定义了一个action。而且没有指定该action的type元素 -->
<action path="/login" name="loginForm"
scope="request" validate="true" input="/login.jsp" >
<!-- 定义action内的两个局部forward元素 -->
<forward name="input" path="/login.jsp"/>
<forward name="welcome" path="/welcome.html"/>
</action>
</action-mappings>
<!-- 使用DelegatingRequestProcessor替换RequestProcessor -->
<controller processorClass="org.springframework.web.struts.
DelegatingRequestProcessor"/>
<!-- 加载国际化的资源包 -->
<message-resources parameter="mess"/>
<!-- 装载验证的资源文件 -->
<plug-in className="org.apache.struts.validator.ValidatorPlugIn">
<set-property property="pathnames" value="/WEB-INF/validator-
rules.xml,/WEB-INF/validation.xml" />
<set-property property="stopOnFirstError" value="true"/>
</plug-in>
<!-- 装载Spring配置文件,随应用的启动创建ApplicationContext实例 -->
<plug-in className="org.springframework.web.struts.
ContextLoaderPlugIn">
<set-property property="contextConfigLocation"
value="/WEB-INF/applicationContext.xml,
/WEB-INF/action-servlet.xml"/>
</plug-in>
</struts-config>
修改后的struts-config.xml文件,增加加载国际化资源文件。配置Struts的action不需要class属性,完成了ApplicationContext的创建。
然后考虑web.xml文件的配置,在web.xml文件中必须配置Struts框架的加载。除此之外,因为使用了Spring管理Struts的Action,而Action是随HTTP请求启动的,因此,应将Action的作用域配置成Request,为了使用Request作用域,必须在web.xml文件中增加适当的配置。
下面是web.xml文件的代码:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Web配置文件的根元素,以及对应的Schema信息 -->
<web-app xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<!-- 定义一个Filter,该Filter是使用Request作用域的基础 -->
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.
RequestContextFilter </filter-class>
</filter>
<!-- 定义filter-mapping,让上面的Filter过滤所有的用户请求 -->
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 定义Struts的核心Servlet -->
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet
</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<!-- 定义Struts的核心Servlet拦截所有*.do请求 -->
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<!-- 关于Struts标签库的配置 -->
<jsp-config>
<!-- 配置bean标签 -->
<taglib>
<taglib-uri>/tags/struts-bean</taglib-uri>
<taglib-location>/WEB-INF/struts-bean.tld</taglib-location>
</taglib>
<!-- 配置html标签 -->
<taglib>
<taglib-uri>/tags/struts-html</taglib-uri>
<taglib-location>/WEB-INF/struts-html.tld</taglib-location>
</taglib>
<!-- 配置logic标签 -->
<taglib>
<taglib-uri>/tags/struts-logic</taglib-uri>
<taglib-location>/WEB-INF/struts-logic.tld</taglib-location>
</taglib>
</jsp-config>
</web-app>
posted @
2009-07-19 10:22 jadmin 阅读(65) |
评论 (0) |
编辑 收藏
6.3.2 Spring事务策略的优势
虽然在上面的配置片段中,仅仅配置了JDBC局部事务管理器、Hibernate局部事务管理器、JDBC全局事务管理器等。但Spring支持大部分持久化策略的事务管理器。
不论采用何种持久化策略,Spring都提供了一致的事务抽象,因此,应用开发者能在任何环境下,使用一致的编程模型。无须更改代码,应用就可在不同的事务管理策略中切换。Spring同时支持声明式事务管理和编程式事务管理。
使用编程式事务管理,开发者使用的是Spring事务抽象,而无须使用任何具体的底层事务API。Spring的事务管理将代码从底层具体的事务API中抽象出来,该抽象可以使用在任何底层事务基础之上。
使用声明式策略,开发者通常书写很少的事务管理代码,因此,不依赖Spring或任何其他事务API。Spring的声明式事务无须任何额外的容器支持,Spring容器本身管理声明式事务。使用声明事务策略,无须在业务代码中书写任何事务代码,可以让开发者更好地专注于业务逻辑的实现。Spring管理的事务支持多个事务资源的跨越,但无法支持跨越远程调用的事务上下文传播。
6.3.3 使用TransactionProxyFactoryBean创建事务代理
Spring同时支持编程式事务策略和声明式事务策略,大部分时候,都推荐采用声明式事务策略,使用声明事务策略的优势十分明显:
● 声明式事务能大大降低开发者的代码书写量。而且声明式事务几乎不需要影响应用的代码。因此,无论底层事务策略如何变化,应用程序无须任何改变。
● 应用程序代码无须任何事务处理代码,可以更专注于业务逻辑的实现。
● Spring则可对任何POJO的方法提供事务管理,而且Spring的声明式事务管理无须容器的支持,可在任何环境下使用。
● EJB的CMT无法提供声明式回滚规则。而通过配置文件,Spring可指定事务在遇到特定异常时自动回滚。Spring不仅可在代码中使用setRollbackOnly回滚事务,也可在配置文件中配置回滚规则。
● 由于Spring采用AOP的方式管理事务,因此,可以在事务回滚动作中插入用户自己的动作,而不仅仅是执行系统默认的回滚。
提示:本节不打算全面介绍Spring的各种事务策略,因此本节不会介绍编程式事务。如果读者需要更全面了解Spring事务的相关方面,请参阅笔者所著的《Spring2.0宝典》 一书。
对于采用声明式事务策略,可以使用TransactionProxyFactoryBean来配置事务代理Bean。正如它的类名所暗示的,它是一个工厂Bean,工厂Bean用于生成一系列的Bean实例,这一系列的Bean实例都是Proxy。
可能读者已经想到了,既然TransactionProxyFactoryBean产生的是代理Bean,可见这种事务代理正是基于Spring AOP组件的。配置TransactionProxyFactoryBean时,一样需要指定目标Bean。
每个TransactionProxyFactoryBean为一个目标Bean生成事务代理,事务代理的方法改写了目标Bean的方法,就是在目标Bean的方法执行之前加入开始事务,在目标Bean的方法正常结束之前提交事务,如果遇到特定异常则回滚事务。
TransactionProxyFactoryBean创建事务代理时,需要了解当前事务所处的环境,该环境属性通过PlatformTransactionManager实例传入,而相关事务传入规则在TransactionProxy- FactoryBean的定义中给出。
下面给出声明式事务配置文件的完整代码:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->
<property name="dataSource" <ref="dataSource"/>
<!-- mappingResources属性用来列出全部映射文件 -->
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的PO映射文件 -->
<value>lee/Person.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的连接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根据Hiberante映射创建数据表时,选择create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置DAO Bean,该Bean将作为目标Bean使用 -->
<bean id="personDAOTarget" class="lee.PersonDaoImpl">
<!-- 采用依赖注入来传入SessionFactory的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置Hibernate的事务管理器 -->
<!-- 使用HibernateTransactionManager类,该类实现PlatformTransactionManager
接口,针对采用Hibernate持久化连接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean,它需要依赖注入一个SessionFactory
Bean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
<!-- 配置personDAOTarget Bean的事务代理 -->
<bean id="personDAO"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<!-- 依赖注入PlatformTransactionManager的bean引用,此处使用
Hibernate的bean -->
<!-- 局部事务器,因此transactionManager 传入Hibernate事务管理器的
引用 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 需要生成代理的目标bean -->
<property name="target" ref="personDAOTarget"/>
<!-- 指定事务属性 -->
<property name="transactionAttributes">
<props>
<!-- 以下部分为定义事务回滚规则 -->
<prop key="insert*">PROPAGATION_REQUIRED,
-MyCheckedException</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
</beans>
在上面的定义文件中,没有对DAO对象采用Service层包装。通常情况下,DAO层上应有一层Service层。事务代理则以Service层Bean为目标Bean。此处为了简化配置,TransactionProxyFactoryBean直接以DAO bean作为目标bean,这一点不会影响事务代理的生成。
事务回滚规则部分定义了三个回滚规则:
第一个回滚规则表示所有以insert开始的方法,都应该满足该事务规则。PROPAGATION_REQUIRED事务传播规则指明,该方法必须处于事务环境中,如果当前执行线程已处于事务环境下,则直接执行;否则,启动新的事务然后执行该方法。该规则还指定,如果方法抛出MyCheckedException的实例及其子类的实例,则强制回滚。MyCheckedException前的“-”表示强制回滚;“+”则表示强制提交,即某些情况下,即使抛出异常也强制提交;
第二个回滚规则表示所有以update开头的方法,都遵守PROPAGATION_REQUIRED的事务传播规则;
第三个回滚规则表示除前面规定的方法外,其他所有方法都采用PROPAGATION_ REQUIRED事务传播规则,而且只读。
常见的事务传播规则有如下几个:
● PROPAGATION_MANDATORY,要求调用该方法的线程必须处于事务环境中,否则抛出异常。
● PROPAGATION_NESTED,如果执行该方法的线程已处于事务环境下,依然启动新的事务,方法在嵌套的事务里执行。如果执行该方法的线程序并未处于事务中,也启动新的事务,然后执行该方法,此时与PROPAGATION_REQUIRED相同。
● PROPAGATION_NEVER,不允许调用该方法的线程处于事务环境下,如果调用该方法的线程处于事务环境下,则抛出异常。
● PROPAGATION_NOT_SUPPORTED,如果调用该方法的线程处在事务中,则先暂停当前事务,然后执行该方法。
● PROPAGATION_REQUIRED,要求在事务环境中执行该方法,如果当前执行线程已处于事务中,则直接调用;如果当前执行线程不处于事务中,则启动新的事务后执行该方法。
● PROPAGATION_REQUIRES_NEW,该方法要求有一个线程在新的事务环境中执行,如果当前执行线程已处于事务中,先暂停当前事务,启动新的事务后执行该方法;如果当前调用线程不处于事务中,则启动新的事务后执行该方法。
● PROPAGATION_SUPPORTS,如果当前执行线程处于事务中,则使用当前事务,否则不使用事务。
程序里原来使用personDAO的地方,无须变化。因为,配置文件里将personDAO目标Bean的id改成personDAOTarget,为TransactionProxyFactoryBean工厂Bean所产生的代理Bean命名为personDAO。该代理Bean会包含原有personDAO的所有方法,而且为这些方法增加了不同的事务处理规则。
程序面向PersonDaoImpl类所实现的接口编程,TransactionProxyFactoryBean生成的代理Bean也会实现TransactionProxyFactoryBean接口。因此,原有的程序中调用DAO组件的代码无须任何改变。程序运行时,由事务代理完成原来目标Bean完成的工作。
事实上,Spring不仅支持对接口的代理,整合CGLIB后,Spring甚至可对具体类生成代理。只要设置proxyTargetClass属性为true就可以。如果目标Bean没有实现任何接口,proxyTargetClass属性默认被设为true,此时Spring会对具体类生成代理。当然,通常建议面向接口编程,而不要面向具体的实现类编程。
6.3.4 使用继承简化事务配置
仔细观察配置文件中两个事务代理Bean的配置时,发现两个事务代理Bean的大部分配置完全相同,如果配置文件中包含大量这样的事务代理Bean配置,配置文件将非常臃肿。考虑到大部分事务代理Bean的配置都大同小异,可以使用Bean继承来简化事务代理的配置。
正如前面部分介绍的,Bean继承是将所有子Bean中相同的配置定义成一个模板,并将此模板Bean定义成一个抽象Bean。考虑所有事务代理Bean中,有如下部分是大致相 同的:
● 事务代理Bean所使用的事务管理器。
● 事务传播规则。
因此,现在配置文件中定义如下的事务代理模板Bean,其配置代码如下:
<!-- 定义所有事务代理Bean的模板 -->
<bean id="txProxyTemplate" abstract="true"
class="org.springframework.transaction.interceptor.
TransactionProxyFactoryBean">
<!-- 为事务代理Bean注入生成代理所需的PlatformTransactionManager实例 -->
<property name="transactionManager" ref="transactionManager"/>
<!-- 定义生成事务代理通用的事务属性 -->
<property name="transactionAttributes">
<props>
<!-- 所有的方法都应用PROPAGATION_REQUIRED的事务传播规则 -->
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
而真正的事务代理Bean,则改为继承上面的事务模板Bean。考虑到将目标Bean定义在Spring容器中可能增加未知的风险,因此将目标Bean定义成嵌套Bean。
<!-- 让事务代理Bean继承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
<!-- 这里采用嵌套Bean的方式来定义目标Bean,当然也可以引用已存在的Bean -->
<property name="target">
<bean class="lee.personDAO"/>
</property>
</bean>
此时的personDAO Bean无须具体地定义事务属性,它将在其父Bean txProxyTemplate中获取事务定义属性。此处采用嵌套Bean来定义目标Bean,因此,并未将目标Bean直接暴露在Spring的上下文中让其他模块调用。当然,也可采用一个已经存在的Bean作为目标Bean;子Bean的事务属性定义,完全可覆盖事务代理模板里的事务属性定义。如下例所示:
<!-- 让事务代理bean继承模板Bean -->
<bean id="personDAO" parent="txProxyTemplate">
<!-- 这里,采用引用已存在的bean的方式来定义目标Bean -->
<property name="target" ref ="personDAOTarget"/>
<!-- 覆盖事务代理模板bean中的事务属性定义 -->
<property name="transactionAttributes">
<props>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
可见,采用Bean继承方式定义事务代理的方式,可以很好地简化事务代理的配置,可以避免配置事务代理Bean时的冗余配置。
提示:使用Bean继承可以很好地简化事务代理Bean的配置,通过将各事务代理Bean共同的配置信息提取成事务模板Bean,可以让实际的事务代理Bean的配置更加简洁;而且,配置方式相当直观。尽量将目标Bean配置成嵌套Bean,这样的方式可以保证更好的内聚性。
如果读者还记得前面介绍的AOP知识,应该知道还有一种更加简洁的配置,就是利用Bean后处理器,让Bean后处理器为容器中其他Bean自动创建事务代理。
6.3.5 使用自动创建代理简化事务配置
回顾6.2.6节和6.2.7节,读者可能已经想到如何自动创建代理。是的,正是通过6.2.6节和6.2.7节所给出的两个自动代理创建类来生成事务代理。
正如前文已经提到的,使用BeanNameAutoProxyCreator和DefaultAdvisorAutoProxy- Creator来创建代理时,并不一定是创建事务代理,关键在于传入的拦截器,如果传入事务拦截器,将可自动生成事务代理。
下面是使用BeanNameAutoProxyCreator自动生成事务代理的配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及相应的Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 使用JDBC的局部事务策略 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSource-
TransactionManager">
<!-- 为事务管理器注入所需的数据源Bean -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->
<bean id="test1" class="lee.TransactionTestImpl">
<!-- 依赖注入目标Bean所必需的数据源Bean -->
<property name="ds" ref="dataSource"/>
</bean>
<!-- 配置目标Bean,该目标Bean将由Bean后处理器自动生成代理 -->
<bean id="test2" class="lee.TestImpl">
<!-- 依赖注入目标Bean所必需的数据源Bean -->
<property name="ds" ref="dataSource"/>
</bean>
<!-- 配置事务拦截器Bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事务拦截器bean需要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator的Bean后处理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对满足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<!-- 下面是所有需要自动创建事务代理的Bean -->
<list>
<value>test1</value>
<value>test2</value>
</list>
<!-- 此处可增加其他需要自动创建事务代理的Bean -->
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的Interceptor -->
</list>
</property>
</bean>
</beans>
如果配置文件中仅有两个目标Bean,可能不能很清楚地看出这种自动创建代理配置方式的优势,但如果有更多目标Bean需要自动创建事务代理,则可以很好地体会到这种配置方式的优势:配置文件只需要简单地配置目标Bean,然后在BeanNameAutoProxyCreator配置中增加一行即可。
提示:使用BeanNameAutoProxyCreator可以自动创建事务代理,使用DefaultAdvisor- AutoProxyCreator也可自动创建事务代理。关于后一个Bean后处理器的配置方式,请参看前面6.2.7节的内容。
posted @
2009-07-19 10:18 jadmin 阅读(76) |
评论 (0) |
编辑 收藏
6.3 Spring的事务
Spring的事务管理不需与任何特定的事务API耦合。对不同的持久层访问技术,编程式事务提供一致的事务编程风格,通过模板化的操作一致性地管理事务。声明式事务基于Spring AOP实现,却并不需要程序开发者成为AOP专家,亦可轻易使用Spring的声明式事务管理。
6.3.1 Spring支持的事务策略
Spring事务策略是通过PlatformTransactionManager接口体现的,该接口是Spring事务策略的核心。该接口的源代码如下:
public interface PlatformTransactionManager
{
//平台无关的获得事务的方法
TransactionStatus getTransaction(TransactionDefinition definition)
throws TransactionException;
//平台无关的事务提交方法
void commit(TransactionStatus status) throws TransactionException;
//平台无关的事务回滚方法
void rollback(TransactionStatus status) throws TransactionException;
}
PlatformTransactionManager是一个与任何事务策略分离的接口,随着底层不同事务策略切换,应用必须采用不同的实现类。PlatformTransactionManager接口没有与任何事务资源捆绑在一起,它可以适应于任何的事务策略,结合Spring的IoC容器,可以向PlatformTransactionManager注入相关的平台特性。
PlatformTransactionManager接口有许多不同的实现类,应用程序面向与平台无关的接口编程,对不同平台的底层支持,由PlatformTransactionManager接口的实现类完成。从而,应用程序无须与具体的事务API耦合。因此,使用PlatformTransactionManager接口,可将代码从具体的事务API中解耦出来。
即使使用特定容器管理的JTA,代码依然无须执行JNDI查找,无须与特定的JTA资源耦合在一起。通过配置文件,JTA资源传给PlatformTransactionManager的实现类。因此,程序的代码可在JTA事务管理和非JTA事务管理之间轻松切换。
在PlatformTransactionManager接口内,包含一个getTransaction(TransactionDefinition definition)方法,该方法根据一个TransactionDefinition参数,返回一个TransactionStatus对象。TransactionStatus对象表示一个事务。TransactionStatus被关联在当前执行的线程。
getTransaction(TransactionDefinition definition)返回的TransactionStatus对象,可能是一个新的事务,也可能是一个已经存在的事务对象。如果当前执行的线程已经处于事务管理下,返回当前线程的事务对象,否则,返回当前线程的调用堆栈已有的事务对象。
TransactionDefinition接口定义了一个事务规则,该接口必须指定如下几个属性值:
● 事务隔离,当前事务和其他事务的隔离程度。例如,这个事务能否看到其他事务未提交的数据等。
● 事务传播,通常,在事务中执行的代码都会在当前事务中运行。但是,如果一个事务上下文已经存在,有几个选项可指定该事务性方法的执行行为。例如,大多数情况下,简单地在现有的事务上下文中运行;或者挂起现有事务,创建一个新的事务。Spring提供EJB CMT(Contain Manager Transaction,容器管理事务)中所有的事务传播选项。
● 事务超时,事务在超时前能运行多久。事务的最长持续时间。如果事务一直没有被提交或回滚,将在超出该时间后,系统自动回滚事务。
● 只读状态,只读事务不修改任何数据。在某些情况下(例如使用Hibernate时),只读事务是非常有用的优化。
TransactionStatus代表事务本身,它提供了简单的控制事务执行和查询事务状态的方法。这些方法在所有的事务API中都是相同的。TransactionStatus接口的源代码如下:
public interface TransactionStatus
{
//判断事务是否是新建的事务
boolean isNewTransaction();
//设置事务回滚
void setRollbackOnly();
//查询事务是否已有回滚标志
boolean isRollbackOnly();
}
Spring的事务管理由PlatformTransactionManager的不同实现类完成。在Spring上下文中配置PlatformTransactionManager Bean时,必须针对不同环境提供不同的实现类。
下面提供不同的持久层访问环境,及其对应的PlatformTransactionManager实现类的 配置。
JDBC数据源的局部事务策略:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 配置JDBC数据源的局部事务管理器 -->
<!-- 使用DataSourceTransactionManager 类,该类实现PlatformTransactionManager接口 -->
<!-- 针对采用数据源连接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.
DataSourceTransactionManager">
<!-- DataSourceTransactionManager bean需要依赖注入一个DataSource
bean的引用 -->
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>
对于容器管理JTA数据源,全局事务策略的配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 配置JNDI数据源Bean -->
<bean id="dataSource" class="org.springframework.jndi.
JndiObjectFactoryBean">
<!-- 容器管理数据源的JNDI -->
<property name="jndiName" value="jdbc/jpetstore"/>
</bean>
<!-- 使用JtaTransactionManager类,该类实现PlatformTransactionManager接
口 -->
<!-- 针对采用全局事务管理的特定实现 -->
<!-- JtaTransactionManager不需要知道数据源,或任何其他特定资源 -->
<!-- 因为它使用容器的全局事务管理 -->
<bean id="transactionManager"
class="org.springframework.transaction.jta.
JtaTransactionManager" />
</beans>
对于采用Hibernate持久层访问策略时,局部事务策略的配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="jdbc:mysql://localhost/j2ee"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="root"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="32147"/>
<!-- 指定连接数据库连接池的最大连接数 -->
<property name="maxPoolSize" value="40"/>
<!-- 指定连接数据库连接池的最小连接数 -->
<property name="minPoolSize" value="1"/>
<!-- 指定连接数据库连接池的初始化连接数 -->
<property name="initialPoolSize" value="1"/>
<!-- 指定连接数据库连接池的连接最大空闲时间 -->
<property name="maxIdleTime" value="20"/>
</bean>
<!-- 定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出全部映射文件 -->
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory的属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的连接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根据Hibernate映射创建数据表时,选择create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置Hibernate的局部事务管理器 -->
<!-- 使用HibernateTransactionManager类,该类是PlatformTransactionManager
接口,针对采用Hibernate持久化连接的特定实现 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- HibernateTransactionManager Bean需要依赖注入一个
SessionFactorybean的引用 -->
<property name="sessionFactory" ref="sessionFactory"/>
</bean>
</beans>
对于采用Hibernate持久层访问策略时,全局事务策略的配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 配置JNDI数据源Bean -->
<bean id="dataSource" class="org.springframework.jndi.
JndiObjectFactoryBean">
<!-- 容器管理数据源的JNDI -->
<property name="jndiName" value="jdbc/jpetstore"/>
</bean>
<!--定义Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate3.
LocalSessionFactoryBean">
<!-- 依赖注入SessionFactory所需的数据源,正是上文定义的dataSource Bean -->
<property name="dataSource" ref="dataSource"/>
<!-- mappingResources属性用来列出全部映射文件 -->
<property name="mappingResources">
<list>
<!-- 以下用来列出所有的PO映射文件 -->
<value>lee/MyTest.hbm.xml</value>
</list>
</property>
<!-- 定义Hibernate的SessionFactory的属性 -->
<property name="hibernateProperties">
<props>
<!-- 指定Hibernate的连接方言 -->
<prop key="hibernate.dialect">org.hibernate.dialect.
MySQLDialect</prop>
<!-- 是否根据Hiberante映射创建数据表时,选择create、update、
create-drop -->
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 使用JtaTransactionManager类,该类是PlatformTransactionManager接口,
针对采用数据源连接的特定实现 -->
<!-- JtaTransactionManager不需要知道数据源,或任何其他特定资源,
因为使用容器的全局事务管理 -->
<bean id="transactionManager"
class="org.springframework.transaction.jta.
JtaTransactionManager" />
</beans>
不论采用哪种持久层访问技术,只要使用JTA数据源,Spring事务管理器的配置都是一样的,因为它们都采用的是全局事务管理。
可以看到,仅仅通过配置文件的修改,就可以在不同的事务管理策略间切换,即使从局部事务到全局事务的切换。
提示:Spring所支持的事务策略非常灵活,Spring的事务策略允许应用程序在不同事务策略之间自由切换,即使需要在局部事务策略和全局事务策略之间切换,只需要修改配置文件,而应用程序的代码无须任何改变。这种灵活的设计,又何尝不是因为面向接口编程带来的优势,可见面向接口编程给应用程序更好的适应性。
posted @
2009-07-19 10:18 jadmin 阅读(72) |
评论 (0) |
编辑 收藏
6.2.4 代理接口
当目标Bean的实现类实现了接口后,Spring AOP可以为其创建JDK动态代理,而无须使用CGLIB创建的代理,这种代理称为代理接口。
创建AOP代理必须指定两个属性:目标Bean和处理。实际上,很多AOP框架都以拦截器作为处理。因为Spring AOP与IoC容器的良好整合,因此配置代理Bean时,完全可以利用依赖注入来管理目标Bean和拦截器Bean。
下面的示例演示了基于AOP的权限认证,它是简单的TestService接口,该接口模拟Service组件,该组件内包含两个方法:
● 查看数据。
● 修改数据。
接口的源代码如下:
//Service组件接口
public interface TestService
{
//查看数据
void view();
//修改数据
void modify();
}
该接口的实现类实现两个方法。因为篇幅限制,本示例并未显示出完整的查看数据和修改数据的持久层操作,仅仅在控制台打印两行信息。实际的项目实现中,两个方法的实现则改成对持久层组件的调用,这不会影响示例程序的效果。实现类的源代码如下:
TestService接口的实现类
public class TestServiceImpl implements TestService
{
//实现接口必须实现的方法
public void view()
{
System.out.println("用户查看数据");
}
//实现接口必须实现的方法
public void modify()
{
System.out.println("用户修改数据");
}
}
示例程序采用Around 处理作为拦截器,拦截器中使用依赖注入获得当前用户名。实际Web应用中,用户应该从session中读取。这不会影响示例代码的效果。拦截器源代码如下:
public class AuthorityInterceptor implements MethodInterceptor
{
//当前用户名
private String user;
//依赖注入所必需的setter方法
public void setUser(String user)
{
this.user = user;
}
public Object invoke(MethodInvocation invocation) throws Throwable
{
//获取当前拦截的方法名
String methodName = invocation.getMethod().getName();
//下面执行权限检查
//对既不是管理员,也不是注册用户的情况
if (!user.equals("admin") && !user.equals("registedUser"))
{
System.out.println("您无权执行该方法");
return null;
}
//对仅仅是注册用户,调用修改数据的情况
else if (user.equals("registedUser") && methodName.equals
("modify"))
{
System.out.println("您不是管理员,无法修改数据");
return null;
}
//对管理员或注册用户,查看数据的情况
else
{
return invocation.proceed();
}
}
}
TestAction类依赖TestService。因篇幅关系,此处不给出TestAction的接口的源代码,TestActionImpl的源代码如下:
public class TestActionImpl
{
//将TestService作为成员变量,面向接口编程
private TestService ts;
//依赖注入的setter方法
public void setTs(TestService ts)
{
this.ts = ts;
}
//修改数据
public void modify()
{
ts.modify();
}
//查看数据
public void view()
{
ts.view();
}
}
配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 配置目标Bean -->
<bean id="serviceTarget" class="lee.TestServiceImpl"/>
<!-- 配置拦截器,拦截器作为处理使用 -->
<bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
<property name="user" value="admin"/>
</bean>
<!-- 配置代理工厂Bean,负责生成AOP代理 -->
<bean id="service" class="org.springframework.aop.framework.
ProxyFactoryBean">
<!-- 指定AOP代理所实现的接口 -->
<property name="proxyInterfaces" value="lee.TestService"/>
<!-- 指定AOP代理所代理的目标Bean -->
<property name="target" ref="serviceTarget"/>
<!-- AOP代理所需要的拦截器列表 -->
<property name="interceptorNames">
<list>
<value>authorityInterceptor</value>
</list>
</property>
</bean>
<!-- 配置Action Bean,该Action依赖TestService Bean -->
<bean id="testAction" class="lee.TestActionImpl">
<!-- 此处注入的是依赖代理Bean -->
<property name="ts" ref="service"/>
</bean>
</beans>
主程序请求testAction Bean,然后调用该Bean的两个方法,主程序如下:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
//创建Spring容器实例
ApplicationContext ctx = new FileSystemXmlApplicationContext
("bean.xml");
//获得TestAction bean
TestAction ta = (TestAction)ctx.getBean("testAction");
//调用bean的两个测试方法
ta.view();
ta.modify();
}
}
程序执行结果如下:
[java] 用户查看数据
[java] 用户修改数据
代理似乎没有发挥任何作用。因为配置文件中的当前用户是admin,admin用户具备访问和修改数据的权限,因此代理并未阻止访问。将配置文件中的admin修改成registed- User,再次执行程序,得到如下结果:
[java] 用户查看数据
[java] 您不是管理员,无法修改数据
代理阻止了registedUser修改数据,查看数据可以执行。将registedUser修改成其他用户,执行程序,看到如下结果:
[java] 您无权执行该方法
[java] 您无权执行该方法
代理阻止用户对两个方法的执行。基于AOP的权限检查,可以降低程序的代码量,因为无须每次调用方法之前,手动编写权限检查代码;同时,权限检查与业务逻辑分离,提高了程序的解耦。
示例中的目标Bean被暴露在容器中,可以被客户端代码直接访问。为了避免客户端代码直接访问目标Bean,可以将目标Bean定义成代理工厂的嵌套Bean,修改后的配置文件如下:
<?xml version="1.0" encoding="GBK"?>
<!-- 指定Spring配置文件的根元素,以及Spring配置文件的Schema信息 -->
<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.xsd">
<!-- 配置拦截器,拦截器作为处理使用 -->
<bean id="authorityInterceptor" class="lee.AuthorityInterceptor">
<property name="user" value="admin"/>
</bean>
<!-- 配置代理工厂Bean,该工厂Bean将负责创建目标Bean的代理 -->
<bean id="service" class="org.springframework.aop.framework.
ProxyFactoryBean">
<!-- 指定AOP代理所实现的接口 -->
<property name="proxyInterfaces" value="lee.TestService"/>
<property name="target">
<!-- 以嵌套Bean的形式定义目标Bean,避免客户端直接访问目标Bean -->
<bean class="lee.TestServiceImpl"/>
</property>
<!-- AOP代理所需要的拦截器列表 -->
<property name="interceptorNames">
<list>
<value>authorityInterceptor</value>
</list>
</property>
</bean>
<!-- 配置Action Bean,该Action依赖TestService Bean -->
<bean id="testAction" class="lee.TestActionImpl">
<!-- 此处注入的是依赖代理Bean -->
<property name="ts" ref="service"/>
</bean>
</beans>
由上面介绍的内容可见,Spring的AOP是对JDK动态代理模式的深化。通过Spring AOP组件,允许通过配置文件管理目标Bean和AOP所需的处理。
下面将继续介绍如何为没有实现接口的目标Bean创建CGLIB代理。
6.2.5 代理类
如果目标类没有实现接口,则无法创建JDK动态代理,只能创建CGLIB代理。如果需要没有实现接口的Bean实例生成代理,配置文件中应该修改如下两项:
● 去掉<property name="proxyInterfaces"/>声明。因为不再代理接口,因此,此处的配置没有意义。
● 增加<property name="proxyTargetClass">子元素,并设其值为true,通过该元素强制使用CGLIB代理,而不是JDK动态代理。
注意:最好面向接口编程,不要面向类编程。同时,即使在实现接口的情况下,也可强制使用CGLIB代理。
CGLIB代理在运行期间产生目标对象的子类,该子类通过装饰器设计模式加入到Advice中。因为CGLIB代理是目标对象的子类,则必须考虑保证如下两点:
● 目标类不能声明成final,因为final类不能被继承,无法生成代理。
● 目标方法也不能声明成final,final方法不能被重写,无法得到处理。
当然,为了需要使用CGLIB代理,应用中应添加CGLIB二进制Jar文件。
6.2.6 使用BeanNameAutoProxyCreator自动创建代理
这是一种自动创建事务代理的方式,一旦在容器中配置了BeanNameAutoProxyCreator实例,该实例将会对指定名字的Bean实例自动创建代理。实际上,BeanNameAutoProxyCreator是一个Bean后处理器,理论上它会对容器中所有的Bean进行处理,实际上它只对指定名字的Bean实例创建代理。
BeanNameAutoProxyCreator根据名字自动生成事务代理,名字匹配支持通配符。
与ProxyFactoryBean一样,BeanNameAutoProxyCreator需要一个interceptorNames属性,该属性名虽然是“拦截器”,但并不需要指定拦截器列表,它可以是Advisor或任何处理类型。
下面是使用BeanNameAutoProxyCreator的配置片段:
<!-- 定义事务拦截器bean -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<!-- 事务拦截器bean需要依赖注入一个事务管理器 -->
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributes">
<!-- 下面定义事务传播属性 -->
<props>
<prop key="insert*">PROPAGATION_REQUIRED </prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>
<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,
负责为容器中特定的Bean创建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对满足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<list>
<!-- 下面是所有需要自动创建事务代理的Bean -->
<value>core-services-applicationControllerSevice</value>
<value>core-services-deviceService</value>
<value>core-services-authenticationService</value>
<value>core-services-packagingMessageHandler</value>
<value>core-services-sendEmail</value>
<value>core-services-userService</value>
<!-- 此处可增加其他需要自动创建事务代理的Bean -->
</list>
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的Interceptor -->
</list>
</property>
</bean>
上面的片段是使用BeanNameAutoProxyCreator自动创建事务代理的片段。Transaction- Interceptor用来定义事务拦截器,定义事务拦截器时传入事务管理器Bean,也指定事务传播属性。
通过BeanNameAutoProxyCreator定义Bean后处理器,定义该Bean后处理器时,通过beanNames属性指定有哪些目标Bean生成事务代理;还需要指定“拦截器链”,该拦截器链可以由任何处理组成。
定义目标Bean还可使用通配符,使用通配符的配置片段如下所示:
<!-- 定义BeanNameAutoProxyCreator Bean,它是一个Bean后处理器,
负责为容器中特定的Bean创建AOP代理 -->
<bean class="org.springframework.aop.framework.autoproxy.
BeanNameAutoProxyCreator">
<!-- 指定对满足哪些bean name的bean自动生成业务代理 -->
<property name="beanNames">
<!-- 此处使用通配符确定目标bean -->
<value>*DAO,*Service,*Manager</value>
</property>
<!-- 下面定义BeanNameAutoProxyCreator所需的事务拦截器 -->
<property name="interceptorNames">
<list>
<value>transactionInterceptor</value>
<!-- 此处可增加其他新的Interceptor -->
</list>
</property>
</bean>
上面的配置片段中,所有名字以DAO、Service、Manager结尾的bean,将由该“bean后处理器”为其创建事务代理。目标bean不再存在,取而代之的是目标bean的事务代理。通过这种方式,不仅可以极大地降低配置文件的繁琐,而且可以避免客户端代码直接调用目标bean。
注意:虽然上面的配置片段是为目标对象自动生成事务代理。但这不是唯一的,如果有需要,可以为目标对象生成任何的代理。BeanNameAutoProxyCreator为目标对象生成怎样的代理,取决于传入怎样的处理Bean,如果传入事务拦截器,则生成事务代理Bean;否则将生成其他代理,在后面的实例部分,读者将可以看到大量使用BeanNameAutoProxy- Creator创建的权限检查代理。
6.2.7 使用DefaultAdvisorAutoProxyCreator自动创建代理
Spring还提供了另一个Bean后处理器,它也可为容器中的Bean自动创建代理。相比之下,DefaultAdvisorAutoProxyCreator是更通用、更强大的自动代理生成器。它将自动应用于当前容器中的advisor,不需要在DefaultAdvisorAutoProxyCreator定义中指定目标Bean的名字字符串。
这种定义方式有助于配置的一致性,避免在自动代理创建器中重复配置目标Bean 名。
使用该机制包括:
● 配置DefaultAdvisorAutoProxyCreator bean定义。
● 配置任何数目的Advisor,必须是Advisor,不仅仅是拦截器或其他处理。因为,必须使用切入点检查处理是否符合候选Bean定义。
DefaultAdvisorAutoProxyCreator计算Advisor包含的切入点,检查处理是否应该被应用到业务对象,这意味着任何数目的Advisor都可自动应用到业务对象。如果Advisor中没有切入点符合业务对象的方法,这个对象就不会被代理。如果增加了新的业务对象,只要它们符合切入点定义,DefaultAdvisorAutoProxyCreator将自动为其生成代理,无须额外 配置。
当有大量的业务对象需要采用相同处理,DefaultAdvisorAutoProxyCreator是非常有用的。一旦定义恰当,直接增加业务对象,而不需要额外的代理配置,系统自动为其增加 代理。
<beans>
<!-- 定义Hibernate局部事务管理器,可切换到JTA全局事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.hibernate3.
HibernateTransactionManager">
<!-- 定义事务管理器时,依赖注入SessionFactory -->
<property name="sessionFactory" ref bean="sessionFactory"/>
</bean>
<!-- 定义事务拦截器 -->
<bean id="transactionInterceptor"
class="org.springframework.transaction.interceptor.
TransactionInterceptor">
<property name="transactionManager" ref="transactionManager"/>
<property name="transactionAttributeSource">
<props>
<prop key="*">PROPAGATION_REQUIRED</prop>
<prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
</props>
</property>
</bean>
<!-- 定义DefaultAdvisorAutoProxyCreator Bean,这是一个Bean后处理器 -->
<bean class="org.springframework.aop.framework.autoproxy.
DefaultAdvisorAutoProxyCreator"/>
<!-- 定义事务Advisor -->
<bean class="org.springframework.transaction.interceptor.
TransactionAttributeSourceAdvisor">
<property name="transactionInterceptor" ref=
"transactionInterceptor"/>
</bean>
<!-- 定义额外的Advisor>
<bean id="customAdvisor" class="lee.MyAdvisor"/>
</beans>
DefaultAdvisorAutoProxyCreator支持过滤和排序。如果需要排序,可让Advisor实现org.springframework.core.Ordered接口来确定顺序。TransactionAttributeSourceAdvisor已经实现Ordered接口,因此可配置其顺序,默认是不排序。
采用这样的方式,一样可以避免客户端代码直接访问目标Bean,而且配置更加简洁。只要目标Bean符合切入点检查,Bean后处理器自动为目标Bean创建代理,无须依次指定目标Bean名。
注意:Spring也支持以编程方式创建AOP代理,但这种方式将AOP代理所需要的目标对象和处理Bean等对象的耦合降低到代码层次,因此不推荐使用。如果读者需要深入了解如何通过编程方式创建AOP代理,请参阅笔者所著的《Spring2.0宝典》。
posted @
2009-07-19 10:16 jadmin 阅读(93) |
评论 (0) |
编辑 收藏
6.2 Spring的AOP
AOP(Aspect Orient Programming),也就是面向切面编程,作为面向对象编程的一种补充。问世的时间并不太长,甚至在国内的翻译还不太统一(有些书翻译成面向方面编程),但它确实极好地补充了面向对象编程的方式。面向对象编程将程序分解成各个层次的对象,而面向切面编程将程序运行过程分解成各个切面。
可以这样理解,面向对象编程是从静态角度考虑程序结构,面向切面编程是从动态角度考虑程序运行过程。
Spring AOP是Spring框架的一个重要组件,极好地补充了Spring IoC容器的功能。Spring AOP将Spring IoC容器与AOP组件紧密结合,丰富了IoC容器的功能。当然,即使不使用AOP组件,依然可以使用Spring的IoC容器。
6.2.1 AOP的基本概念
AOP从程序运行角度考虑程序的流程,提取业务处理过程的切面。AOP面向的是程序运行中各个步骤,希望以更好的方式来组合业务处理的各个步骤。
AOP框架并不与特定的代码耦合,AOP框架能处理程序执行中的特定点,而不是某个具体的程序。AOP框架具有如下两个特征:
● 各步骤之间的良好隔离性。
● 源代码无关性。
下面是关于面向切面编程的一些术语:
● 切面,业务流程运行的某个特定步骤,就是运行过程的关注点,关注点可能横切多个对象。
● 连接点,程序执行过程中明确的点,如方法的调用或异常的抛出。Spring AOP中,连接点总是方法的调用,Spring并没有显式地使用连接点。
● 处理(Advice),AOP框架在特定的连接点执行的动作。处理有around、before和throws等类型。大部分框架都以拦截器作为处理模型。
● 切入点,系列连接点的集合,它确定处理触发的时机。AOP框架允许开发者自己定义切入点,如使用正则表达式。
● 引入,添加方法或字段到被处理的类。Spring允许引入新的接口到任何被处理的对象。例如,可以使用一个引入,使任何对象实现IsModified接口,以此来简化缓存。
● 目标对象,包含连接点的对象。也称为被处理对象或被代理对象。
● AOP代理,AOP框架创建的对象,包含处理。简单地说,代理就是对目标对象的加强。Spring中的AOP代理可以是JDK动态代理,也可以是CGLIB代理。前者为实现接口的目标对象的代理,后者为不实现接口的目标对象的代理。
注意:面向切面编程是比较前沿的知识,而国内大部分翻译人士翻译计算机文献时,总是一边开着各种词典和翻译软件,一边逐词去看文献,不是先从总体上把握知识的架构。因此,难免导致一些术语的翻译词不达意,例如,Socket被翻译成“套接字”等。在面向切面编程的各术语翻译上,也存在较大的差异。对于Advice一词,有翻译为“通知”的,有翻译为“建议”的,如此种种,不一而足。实际上,Advice指AOP框架在特定切面所做的事情,故而笔者翻译为“处理”,希望可以表达Advice的真正含义。
6.2.2 AOP的代理
所谓AOP代理,就是AOP框架动态创建的对象,这个对象通常可以作为目标对象的替代品,而AOP代理提供比目标对象更加强大的功能。真实的情形是,当应用调用AOP代理的方法时,AOP代理会在自己的方法中回调目标对象的方法,从而完成应用的调用。
关于AOP代理的典型例子就是Spring中的事务代理Bean。通常,目标Bean的方法不是事务性的,而AOP代理包含目标Bean的全部方法,而且这些方法经过加强变成了事务性方法。简单地说,目标对象是蓝本,AOP代理是目标对象的加强,在目标对象的基础上,增加属性和方法,提供更强大的功能。
目标对象包含一系列切入点。切入点可以触发处理连接点集合。用户可以自己定义切入点,如使用正则表达式。AOP代理包装目标对象,在切入点处加入处理。在切入点加入的处理,使得目标对象的方法功能更强。
Spring默认使用JDK动态代理实现AOP代理,主要用于代理接口。也可以使用CGLIB代理。实现类的代理,而不是接口。如果业务对象没有实现接口,默认使用CGLIB代理。但面向接口编程是良好的习惯,尽量不要面向具体类编程。因此,业务对象通常应实现一个或多个接口。
下面是一个简单动态代理模式的示例,首先有一个Dog的接口,接口如下:
public interface Dog
{
//info方法声明
public void info();
//run方法声明
public void run();
}
然后,给出该接口的实现类,实现类必须实现两个方法,源代码如下:
public class DogImpl implements Dog
{
//info方法实现,仅仅打印一个字符串
public void info()
{
System.out.println("我是一只猎狗");
}
//run方法实现,仅仅打印一个字符串
public void run()
{
System.out.println("我奔跑迅速");
}
}
上面的代码没有丝毫独特之处,是典型的面向接口编程的模型,为了有更好的解耦,采用工厂来创建Dog实例。工厂源代码如下:
public class DogFactory
{
//工厂本身是单态模式,因此,将DogFactory作为静态成员变量保存
private static DogFactory df;
//将Dog实例缓存
private Dog gundog;
//默认的构造器,单态模式需要的构造器是private
private DogFactory()
{
}
//单态模式所需的静态方法,该方法是创建本类实例的唯一方法点
public static DogFactory instance()
{
if (df == null)
{
df = new DogFactory();
}
return df;
}
//获得Dog实例
public Dog getDog(String dogName)
{
//根据字符串参数决定返回的实例
if (dogName.equals("gundog"))
{
//返回Dog实例之前,先判断缓存的Dog是否存在,如果不存在才创建,
否则直接返回缓存的Dog实例
if (gundog == null )
{
gundog = new DogImpl();
}
return gundog;
}
return null;
}
}
下面是一个通用的处理类,该处理类没有与任何特定的类耦合,它可以处理所有的目标对象。从JDK 1.3起,Java的import java.lang.reflect下增加InvocationHandler接口,该接口是所有处理类的根接口。
该类处理类的源代码如下:
public class ProxyHandler implements InvocationHandler
{
//需被代理的目标对象
private Object target;
//执行代理的目标方法时,该invoke方法会被自动调用
public Object invoke(Object proxy, Method method, Object[] args)throws
Exception
{
Object result = null;
if (method.getName().equals("info"))
{
System.out.println("======开始事务...");
result =method.invoke(target, args);
System.out.println("======提交事务...");
}
else
{
result =method.invoke(target, args);
}
return result;
}
//通过该方法,设置目标对象
public void setTarget(Object o)
{
this.target = o;
}
}
该处理类实现InvocationHandler接口,实现该接口必须实现invoke(Object proxy, Method method, Object[] args)方法,程序调用代理的目标方法时,自动变成调用invoke方法。
该处理类并未与任何接口或类耦合,它完全是通用的,它的目标实例是Object类型,可以是任何的类型。
在invoke方法内,对目标对象的info方法进行简单加强,在开始执行目标对象的方法之前,先打印开始事务,执行目标对象的方法之后,打印提交事务。
通过method对象的invoke方法,可以完成目标对象的方法调用,执行代码如下:
result =method.invoke(target, args);
下面是代理工厂:
public class MyProxyFactory
{
/**
* 实例Service对象
* @param serviceName String
* @return Object
*/
public static Object getProxy(Object object)
{
//代理的处理类
ProxyHandler handler = new ProxyHandler();
//把该dog实例托付给代理操作
handler.setTarget(object);
//第一个参数是用来创建动态代理的ClassLoader对象,只要该对象能访问Dog接口
即可
//第二个参数是接口数组,正是代理该接口数组
//第三个参数是代理包含的处理实例
return Proxy.newProxyInstance(DogImpl.class.getClassLoader(),
object.getClass().getInterfaces(),handler);
}
}
代理工厂里有一行代码:
Proxy.newProxyInstance(object.getClass().getClassLoader(),object.getClass().getInterfaces(),handler);
Proxy.newProxyInstance()方法根据接口数组动态创建代理类实例,接口数组通过object.getClass().getInterfaces()方法获得,创建的代理类是JVM在内存中动态创建的,该类实现传入接口数组的全部接口。
因此,Dynamic Proxy要求被代理的必须是接口的实现类,否则无法为其构造相应的动态类。因此,Spring对接口实现类采用Dynamic Proxy实现AOP,而对没有实现任何接口的类,则通过CGLIB实现AOP代理。
下面是主程序:
public class TestDog
{
public static void main(String[] args)
{
Dog dog = null;
//创建Dog实例,该实例将作为被代理对象
Dog targetObject = DogFactory.instance().getDog("gundog");
//以目标对象创建代理
Object proxy = MyProxyFactory.getProxy(targetObject);
if (proxy instanceof Dog)
{
dog = (Dog)proxy;
}
//测试代理的方法
dog.info();
dog.run();
}
}
代理实例会实现目标对象实现的全部接口。因此,代理实例也实现了Dog接口,程序运行结果如下:
[java] ======开始事务...
[java] 我是一只猎狗
[java] ======提交事务...
[java] 我奔跑迅速
代理实例加强了目标对象的方法——仅仅打印了两行字符串。当然,此种加强没有实际意义。试想一下,若程序中打印字符串的地方,换成真实的事务开始和事务提交,则代理实例的方法为目标对象的方法增加了事务性。
6.2.3 创建AOP代理
通过前面的介绍,AOP代理就是由AOP框架动态生成的一个对象,该对象可作为目标对象使用,AOP代理包含了目标对象的全部方法。但AOP代理中的方法与目标对象的方法存在差异:AOP方法在特定切面插入处理,在处理之间回调目标对象的方法。
AOP代理所包含的方法与目标对象所包含的方法的示意图,如图6.1所示。
Spring中AOP代理由Spring的IoC容器负责生成和管理,其依赖关系也由IoC容器负责管理。因此,AOP代理能够引用容器中的其他Bean实例,这种引用由IoC容器的依赖注入提供。
Spring的AOP代理大都由ProxyFactoryBean工厂类产生,如图6.1所示,产生一个AOP代理至少有两个部分:目标对象和AOP框架所加入的处理。因此,配置ProxyFactoryBean时需要确定如下两个属性:
● 代理的目标对象。
● 处理(Advice)。
注意:关于Advice的更多知识,此处由于篇幅原因,无法深入讨论。实际上,Spring也提供了很多种Advice的实现,如需要更深入了解Spring AOP,请读者参考笔者所著的《Spring2.0宝典》。
所有代理工厂类的父类是org.springframework.aop.framework.ProxyConfig。因此,该类的属性是所有代理工厂的共同属性,这些属性也是非常关键的,包括:
● proxyTargetClass,确定是否代理目标类,如果需要代理目标是类,该属性设为true,此时需要使用CGLIB生成代理;如果代理目标是接口,该属性设为false,默认是false。
● optimize,确定是否使用强优化来创建代理。该属性仅对CGLIB代理有效;对JDK 动态代理无效。
● frozen,确定是否禁止改变处理,默认是false。
● exposeProxy,代理是否可以通过ThreadLocal访问,如果exposeProxy属性为true,则可通过AopContext.currentProxy()方法获得代理。
● aopProxyFactory,所使用的AopProxyFactory具体实现。该参数用来指定使用动态代理、CGLIB或其他代理策略。默认选择动态代理或CGLIB。一般不需要指定该属性,除非需要使用新的代理类型,才指定该属性。
配置ProxyFactoryBean工厂bean时,还需要指定它的特定属性,ProxyFactoryBean的特定属性如下所示:
● proxyInterfaces,接口名的字符串数组。如果没有确定该参数,默认使用CGLIB代理。
● interceptorNames,处理名的字符串数组。此处的次序很重要,排在前面的处理,优先被调用。此处的处理名,只能是当前工厂中处理的名称,而不能使用bean引用。处理名字支持使用通配符(*)。
● singleton,工厂是否返回单态代理。默认是true,无论 getObject()被调用多少次,将返回相同的代理实例。如果需要使用有状态的处理——例如,有状态的mixin,可改变默认设置,prototype处理。
posted @
2009-07-19 10:15 jadmin 阅读(64) |
评论 (0) |
编辑 收藏
6.1 两种后处理器
Spring 框架提供了很好的扩展性,除了可以与各种第三方框架良好整合外,其IoC容器也允许开发者进行扩展。这种扩展并不是通过实现BeanFactory或ApplicationContext的子类,而是通过两个后处理器对IoC容器进行扩展。Spring提供了两种常用的后处理器:
● Bean后处理器,这种后处理器会对容器中特定的Bean进行定制,例如功能的 加强。
● 容器后处理器,这种后处理器对IoC容器进行特定的后处理。
下面将介绍这两种常用的后处理器以及两种后处理器相关知识。
6.1.1 Bean后处理器
Bean后处理器是一种特殊的Bean,这种特殊的Bean并不对外提供服务,它无须id属性,但它负责对容器中的其他Bean执行后处理,例如为容器中的目标Bean生成代理。这种Bean可称为Bean后处理器,它在Bean实例创建成功后,对其进行进一步的加强 处理。
Bean后处理器必须实现BeanPostProcessor接口。
BeanPostProcessor接口包含两个方法:
● Object postProcessBeforeInitialization(Object bean, String name)throws BeansExce- ption,该方法的第一个参数是系统即将初始化的Bean实例,第二个参数是Bean实例的名字。
● Object postProcessAfterInitialization(Object bean, String name)throws BeansExce- ption,该方法的第一个参数是系统刚完成初始化的Bean实例,第二个参数是Bean实例的名字。
实现该接口的Bean必须实现这两个方法,这两个方法会对容器的Bean进行后处理。两个方法会在目标Bean初始化之前和初始化之后分别调用。这两个方法用于对系统完成的默认初始化进行加强。
注意:Bean后处理器是对IoC容器一种极好的扩展,Bean后处理器可以对容器中的Bean进行后处理,这种后处理完全由开发者决定。
下面将定义一个简单的Bean后处理器,该Bean后处理器将对容器中其他Bean进行后处理。Bean后处理器的代码如下:
//自定义Bean后处理器,负责后处理容器中所有的Bean
public class MyBeanPostProcessor implements BeanPostProcessor
{
//在初始化bean之前,调用该方法
public Object postProcessBeforeInitialization(Object bean , String
beanName)throws BeansException
{
//仅仅打印一行字符串
System.out.println("系统正在准备对" + beanName + "进行初始化...");
return bean;
}
//在初始化bean之后,调用该方法
public Object postProcessAfterInitialization(Object bean , String
beanName)throws BeansException
{
System.out.println("系统已经完成对" + beanName + "的初始化");
//如果系统刚完成初始化的bean是Chinese
if (bean instanceof Chinese)
{
//为Chinese实例设置name属性
Chinese c = (Chinese)bean;
c.setName("wawa");
}
return bean;
}
}
下面是Chinese的源代码,该类实现了InitializingBean接口,还额外提供了一个初始化方法,这两个方法都由Spring容器控制回调。
public class Chinese implements Person,InitializingBean
{
private Axe axe;
private String name;
public Chinese()
{
System.out.println("Spring实例化主调bean:Chinese实例...");
}
public void setAxe(Axe axe)
{
System.out.println("Spring执行依赖关系注入...");
this.axe = axe;
}
public void setName(String name)
{
this.name = name;
}
public void useAxe()
{
System.out.println(name + axe.chop());
}
public void init()
{
System.out.println("正在执行初始化方法 init...");
}
public void afterPropertiesSet() throws Exception
{
System.out.println("正在执行初始化方法 afterPropertiesSet...");
}
}
配置文件如下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 指定Spring 配置文件的dtd>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org/dtd/spring-beans.dtd">
<!-- Spring配置文件的根元素 -->
<beans>
<!-- 配置bean后处理器,可以没有id属性,此处id属性为了后面引用 -->
<bean id="beanPostProcessor" class="lee.MyBeanPostProcessor"/>
<bean id="steelAxe" class="lee.SteelAxe"/>
<bean id="chinese" class="lee.Chinese" init-method="init">
<property name="axe" ref="steelAxe"/>
</bean>
</beans>
本应用的chinese具有两个初始化方法:
● init-method指定初始化方法。
● 实现InitializingBean接口,提供了afterPropertiesSet初始化方法。
MyBeanPostProcessor类实现了BeanPostProcessor接口,并实现了该接口的两个方法,这两个方法分别在初始化方法调用之前和之后得到回调。
注意:上面的配置文件配置Bean后处理器时,依然为Bean处理器指定了id属性,指定id属性是为了方便程序通过该id属性访问Bean后处理器。大部分时候,程序无须手动访问该Bean后处理器,因此无须为其指定id属性。
主程序如下:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
//CLASSPATH路径下的bean.xml文件创建Resource对象
ClassPathResource isr = new ClassPathResource("bean.xml");
//以Resource对象作为参数,创建BeanFactory的实例
XmlBeanFactory factory = new XmlBeanFactory(isr);
//获取Bean后处理器实例
MyBeanPostProcessor beanProcessor =
(MyBeanPostProcessor)factory.getBean("beanPostProcessor");
//注册BeanPostProcessor实例
factory.addBeanPostProcessor(beanProcessor);
System.out.println("程序已经实例化BeanFactory...");
Person p = (Person)factory.getBean("chinese");
System.out.println("程序中已经完成了chinese bean的实例化...");
p.useAxe();
}
}
如果使用BeanFactory作为Spring容器,必须手动注册Bean后处理器,因此在程序中先获取Bean后处理器实例,然后手动注册——这就是在配置文件中指定Bean后处理器id属性的原因。通过BeanFactory的addBeanPostProcessor可以注册BeanPostProcessor实例。程序执行结果如下:
[java] 程序已经实例化BeanFactory...
[java] Spring实例化主调bean:Chinese实例...
[java] Spring实例化依赖bean:SteelAxe实例...
[java] 系统正在准备对steelAxe进行初始化...
[java] 系统已经完成对steelAxe的初始化
[java] Spring执行依赖关系注入...
[java] 系统正在准备对chinese进行初始化...
[java] 正在执行初始化方法 afterPropertiesSet...
[java] 正在执行初始化方法 init...
[java] 系统已经完成对chinese的初始化
[java] 程序中已经完成了chinese bean的实例化...
[java] wawa钢斧砍柴真快
在配置文件中配置chinese实例时,并未指定name属性值。但程序执行时,name属性有了值,这就是Bean后处理器完成的,在Bean后处理器中判断Bean是否是Chinese实例,然后设置它的name属性。
容器中一旦注册了Bean后处理器,Bean后处理器会自动启动,在容器中每个Bean创建时自动工作,完成加入Bean后处理器需要完成的工作。
实现BeanPostProcessor接口的Bean后处理器可对Bean进行任何操作,包括完全忽略这个回调。BeanPostProcessor通常用来检查标记接口或将Bean包装成一个Proxy的事情。Spring的很多工具类,就是通过Bean后处理器完成的。
从主程序中看到,采用BeanFactory作为Spring容器时,必须手动注册BeanPost- Processor。而对于ApplicationContext,则无须手动注册。ApplicationContext可自动检测到容器中的Bean后处理器,自动注册。Bean后处理器会在Bean实例创建时,自动启动。即主程序采用如下代码,效果完全一样:
public class BeanTest
{
public static void main(String[] args)throws Exception
{
ApplicationContext ctx = new ClassPathXmlApplicationContext
("bean.xml");
Person p = (Person)factory.getBean("chinese");
System.out.println("程序中已经完成了chinese bean的实例化...");
p.useAxe();
}
}
使用ApplicationContext作为容器,无须手动注册BeanPostProcessor。因此,如果需要使用Bean后处理器,Spring容器建议使用ApplicationContext,而不是BeanFactory。
6.1.2 Bean后处理器的用处
上一节介绍了一个简单的Bean后处理器,上面的Bean后处理器负责对容器中的Chinese Bean进行后处理,不管Chinese Bean如何初始化,总是将Chinese Bean的name属性设置为wawa。这种后处理看起来作用并不是特别大。
实际上,Bean后处理器完成的工作更加实际,例如生成Proxy。Spring框架本身提供了大量的Bean后处理器,这些后处理器负责对容器中的Bean进行后处理。
下面是Spring提供的两个常用的后处理器:
● BeanNameAutoProxyCreator,根据Bean实例的name属性,创建Bean实例的代理。
● DefaultAdvisorAutoProxyCreator,根据提供的Advisor,对容器中所有的Bean实例创建代理。
上面提供的两个Bean后处理器,都用于根据容器中配置的拦截器创建目标Bean代理,目标代理就在目标Bean的基础上修改得到。
注意:如果需要对容器中某一批Bean进行特定的处理,可以考虑使用Bean后处理器。
6.1.3 容器后处理器
除了上面提供的Bean后处理器外,Spring还提供了一种容器后处理器。Bean后处理器负责后处理容器生成的所有Bean,而容器后处理器则负责后处理容器本身。
容器后处理器必须实现BeanFactoryPostProcessor接口。实现该接口必须实现如下一个方法:
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
实现该方法的方法体就是对Spring容器进行的处理,这种处理可以对Spring容器进行任意的扩展,当然也可以对Spring容器不进行任何处理。
类似于BeanPostProcessor,ApplicationContext可自动检测到容器中的容器后处理器,并且自动注册容器后处理器。但若使用BeanFactory作为Spring容器,则必须手动注册后处理器。
下面定义了一个容器后处理器,这个容器后处理器实现BeanFactoryPostProcessor接口,但并未对Spring容器进行任何处理,只是打印出一行简单的信息。该容器后处理器的代码如下:
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor
{
//容器后处理器对容器进行的处理在该方法中实现
public void postProcessBeanFactory(ConfigurableListableBeanFactory
beanFactory)
throws BeansException
{
System.out.println("程序对Spring所做的BeanFactory的初始化没有意
见...");
}
}
将该Bean作为普通Bean部署在容器中,然后使用ApplicationContext作为容器,容器会自动调用BeanFactoryPostProcessor处理Spring容器。程序执行效果如下:
[java] 程序对Spring所做的BeanFactory的初始化没有意见...
实现BeanFactoryPostProcessor接口的Bean后处理器不仅可对BeanFactory执行后处理,也可以对ApplicationContext容器执行后处理。容器后处理器还可用来注册额外的属性编辑器。
注意:Spring没有提供ApplicationContextPostProcessor。也就是说,对于Application- Context容器,一样使用BeanFactoryPostProcessor作为容器后处理器。
Spring已提供如下两个常用的容器后处理器,包括:
● PropertyResourceConfigurer,属性占位符配置器。
● PropertyPlaceHolderConfigurer,另一种属性占位符配置器。
下面将详细介绍这两种常用的容器后处理器。
6.1.4 属性占位符配置器
Spring提供了PropertyPlaceholderConfigurer,它是一个容器后处理器,负责读取Java属性文件里的属性值,并将这些属性值设置到Spring容器定义中。
通过使用PropertyPlaceholderConfigurer后处理器,可以将Spring配置文件中的部分设置放在属性文件中设置。这种配置方式当然有其优势:可以将部分相似的配置(如数据库的urls、用户名和密码)放在特定的属性文件中,如果只需要修改这部分配置,则无须修改Spring配置文件,修改属性文件即可。
下面的配置文件配置了PropertyPlaceholderConfigurer后处理器,在配置数据源Bean时,使用了属性文件中的属性值。配置文件的代码如下:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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.xsd">
<!-- 配置一个容器后处理器Bean -->
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.
PropertyPlaceholderConfigurer">
<!-- locations属性指定属性文件的位置 -->
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 如果有多个属性文件,依次在下面列出来 -->
</list>
</property>
</bean>
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="${jdbc.driverClassName}"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="${jdbc.url}"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="${jdbc.username}"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
在上面的配置文件中,配置driverClass和jdbcUrl等信息时,并未直接设置这些属性的属性值,而是设置了${jdbc.driverClassName}和${jdbc.url}属性值。这表明Spring容器将从propertyConfigurer指定属性文件中搜索这些key对应的value,并为该Bean的属性值设置这些value值。
如前所述,ApplicationContext会自动检测部署在容器的容器后处理器,无须额外的注册,容器自动注册。因此,只需提供如下Java Properties文件:
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/j2ee
jdbc.username=root
jdbc.password=32147
通过这种方法,可从主XML配置文件中分离出部分配置信息。如果仅需要修改数据库连接属性,则无须修改主XML配置文件,只需要修改属性文件即可。采用属性占位符的配置方式,可以支持使用多个属性文件。通过这种方式,可将配置文件分割成多个属性文件,从而降低修改配置的风险。
注意:对于数据库连接等信息集中的配置,可以将其配置在Java属性文件中,但不要过多地将Spring配置信息抽离到Java属性文件中,否则可能会降低Spring配置文件的可读性。
6.1.5 另一种属性占位符配置器(PropertyOverrideConfigurer)
PropertyOverrideConfigurer是Spring提供的另一个容器后处理器,这个后处理器的额作用与上面介绍的容器后处理器作用大致相同。但也存在些许差别:PropertyOverride- Configurer使用的属性文件用于覆盖XML配置文件中的定义。即PropertyOverride- Configurer允许XML配置文件中有默认的配置信息。
如果PropertyOverrideConfigurer的属性文件有对应配置信息,XML文件中的配置信息被覆盖;否则,直接使用XML文件中的配置信息。使用PropertyOverrideConfigurer的属性文件,应是如下的格式:
beanName.property=value
beanName是属性占位符试图覆盖的Bean名,property是试图覆盖的属性名。看如下配置文件:
<?xml version="1.0" encoding="GBK"?>
<!-- beans是Spring配置文件的根元素,并且指定了Schema信息 -->
<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.xsd">
<!-- 配置一个属性占位符Bean。ApplictionContext能自动识别
PropertyPlaceholderConfigurer Bean -->
<bean id="propertyOverrider"
class="org.springframework.beans.factory.config.
PropertyOverrideConfigurer">
<property name="locations">
<list>
<value>dbconn.properties</value>
<!-- 如果有多个属性文件,依次在下面列出来 -->
</list>
</property>
</bean>
<!-- 定义数据源Bean,使用C3P0数据源实现 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.
ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="dd"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="xx"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="dd"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="xx"/>
</bean>
</beans>
上面的配置文件中,指定数据源Bean的各种属性值时,只是随意指定了几个属性值,很明显通过这几个属性值无法连接到数据库服务。
但因为Spring容器中部署了一个PropertyOverrideConfigurer的容器后处理器,而且Spring容器使用ApplicationContext作为容器,它会自动检测容器中的容器后处理器,无须额外的注册,容器自动注册该后处理器。
PropertyOverrideConfigurer后处理器读取dbconn.properties文件中的属性,用于覆盖目标Bean的属性。因此,如果属性文件中有dataSource Bean属性的设置,则配置文件中指定的属性值将没有任何作用。
dbconn.properties属性文件如下:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://wonder:3306/j2ee
dataSource.username=root
dataSource.password=32147
注意属性文件的格式必须是:
beanName.property=value
也就是说,dataSource必须是容器中真实存在的bean名,否则程序将出错。
注意:程序无法知道BeanFactory定义是否被覆盖。仅仅通过察看XML配置文件,无法知道配置文件的配置信息是否被覆盖。如有多个PorpertyOverrideConfigurer对同一Bean属性定义了覆盖,最后一个覆盖获胜。
posted @
2009-07-19 10:12 jadmin 阅读(117) |
评论 (0) |
编辑 收藏