基于AOP的事务管理
索丙芮
(贵州大学计算机科学与信息学院,贵州 贵阳 550025)
摘 要 为了把影响多个模块的行为封装到一个单独的可重用模块中,使系统拥有更好的模块化、可扩展性和可维护性,面向方面编程(AOP)是一种行之有效的方法。本文介绍AOP的基本概念并举例比较了传统事务管理和Spring AOP在事务管理两种方法的具体应用,介绍了在面向对象的开发过程中使用AOP的方法进行事务管理的优缺点。
关键词 AOP;事务管理;Spring
1 前言
传统的面向对象技术(OOP)很好地解决了软件系统中角色划分的问题。借助于面向对象的分析、设计和实现技术,开发者可以将问题领域的“名词”转换成软件系统中的对象,从而很自然地完成从问题到软件的转换。但是在具体的应用过程中,常会出现这种情况,一个需求会贯穿很多角色。例如在一个系统中常常存在着这样一些公共的需求:日志、安全性、事务管理等,这些功能贯穿整个系统的多个模块,我们将这行为称为“横切关注点”,我们无法通过OOP避免这些重复的冗余代码。然而采用面向切面的处理方法AOP能很好地处理横切关注点问题。
2 AOP简介
AOP(Aspect Oriented Programming,即面向切面的程序设计)为开发者提供了一种模块化横切关注点的机制,并能够将横切关注点织入到面向对象的软件系统中,从而完成横向功能的模块化。AOP和面向对象的程序设计结合起来就可以很好地完成对真实系统的横向和纵向的两个维度的建模,从而更加容易地构建稳定而易维护的软件系统。
通常开发人员可以将一个复杂的系统看作是由多个关注点组合在一起而实现的,即复杂系统的实现可以被分解成多个关注点的分别实现以及这些关注点实现的组合,比如说:一个系统通常会包括业务逻辑、日志记录、安全检查、事务管理等等方面的关注点。开发人员就可以分别实现系统所需要的业务逻辑,安全逻辑等等关注,最后将这些实现组合在一起,构建起目标系统,如图1所示。系统中包括一些相互平行的业务模块,这些模块之间一般并不相互作用。而另外一些功能,如日志记录、安全检查、事务管理等却会横向地切入到多个业务模块之中。正如前面所提到的那样,系统中存在横向和纵向的两个维度的需求,即存在横向和纵向两种关注点。传统的使用面向对象的程序设计会将这两个维度的需求强制地限制在一个一维空间内,即业务逻辑的实现之中,这样会造成一些代码交织的现象。而AOP实现了对横向功能的模块化,结合面向对象的程序设计就可以以多维的方式解决系统多维的需求。
图1 横切关系图
AOP解决这种多维需求的方案就是将横切的关注点从核心的关注点中分离出来。对核心关注点使用传统的基于对象的或者其他方法进行编程,而对于横切的关注点则采用面向切面的方法来进行编程,组装成切面,最后通过一种机制让两种代码重新组合运行,形成最终的软件系统。可见,采用面向切面的方法,就将系统中纵向和横向的需求分离开了。多维的需求被分散在两个空间分别得到实现,也就解决了上面所提到的不匹配的问题。面向切面编程让两种代码重新组合在一起的机制就是织入,它实现了将两个空间联合起来的功能。
3 传统事务的应用
3.1 J2EE可供选择的事务管理器
J2EE为我们提供了多种事务管理器,来满足我们不同的事务管理需求,主要有以下几种:
全局事务:全局事务使用JTA标准,可以和多个事务性资源关联,适合分布式数据库,缺点是代码需要JTA,JTA又通常需要从JNDI获得。这意味着我们为了JTA,需要同时使用JNDI和JTA,显然全部使用全局事务限制了应用代码的重用性,因此EJB CMT它虽然消除了大多数(不是全部)硬编码的方式去控制事务。但重大的缺陷是绑定在JTA和应用服务器环境上。
局部事务:局部事务是和资源相关的,比如和一个JDBC连接关联的事务,容易使用,但是不能用于多个事务性资源,也不能用于全局的JTA事务中,所以不适合分布式数据库。
特定框架的事务:如Hibernate事务,只对该框架适用。
3.2 以传统方式使用JDBC事务管理
这里我们以JDBC事务管理为例,在传统事务管理中,使用JDBC进行数据操作。首先获取DataSource,然后从数据源中得到Connection,我们知道数据源是线程安全的,而连接不是线程安全的,所以对每个请求都是从数据源中重新取出一个连接。一般的数据源由容器进行管理,包括连接池。例如TOMCAT,WebSPHERE,WebLOGIC等这些J2EE商业容器都提供了这个功能。
我们使用JDBC在写代码时,事务管理可能会是这样:
public class MessageService
{
public void insertMessage(Message message)
{
DataSource dataSource = DataSourceFactory. getDataSource();
Connection con = dataSource.getConnection();
conn.setAutoCommit(false);
try{
//核心逻辑
conn.commit();
}catch(SQLException e)
{
conn.rollback();
}finally{
If(conn != null){
conn.close();
}
}
}
}
按照传统方式,所有需要事务管理的地方都要重复添加上面的代码段。而且代码量比较长,容易疏忽,忘掉一些try/catch,引发一些异常无法catch。虽然有时候我们会写DBTool类,来关闭这些资源,并且保证在关闭这些资源时,不向外抛异常,但是这样做会导致额外的麻烦。另外,当系统需要在不同的事务管理策略间切换时,如从局部事务JDBC到全局事务JTA的切换。此时,系统中所有涉及到事务管理的源代码都要重新修改,不利于系统的移植。
4 AOP在事务管理中的应用
4.1 AOP在事务处理中的应用
业务对象划分抽象事务,而无需关注事务涉及的数据库资源,数据访问对象获取接受事务管理的数据库资源,却无需关注数据库资源如何加入系统。基于AOP动态代理的思想,Spring框架对事务管理提供了一致的事务抽象,因此,开发者能够在任何环境下使用一致的编程模型。在Spring框架中仅仅需要做简单的配置,无须更改代码,应用就可在不同的事务管理策略中切换,从而开发出低依赖性的程序,抛开应用服务器。
下面的例子演示了如何通过简单的配置完成业务需求所需要的事务管理。
第一步:声明数据源
<bean id="dataSource" class=
"com.mchange.v2.c3p0.ComboPooledDatasSource" destroy-method="close"/>
第二步:声明一个事务管理类:Spring没有直接管理事务,它有很多可供选择的事务管理器,进而将事务管理的责任委托给使用JDBC,JTA或持久化机制的某个特定平台的事务实现。例如,如果要更换为JTA事务管理器,只需将此处的class类改为相应的类即可。
<bean id="txManager" class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager"/>
第三步:确定事务的传播行为、隔离级别,以及服务对象中各方法所需采用哪种事务来进行管理。
假定,服务对象MessageService的get*方法必须执行在只读事务上下文中,事务传播行为采用默认的需要(REQUIRE)事务上下文中,事务隔离级别采用数据库默认的隔离级别,而更新操作的方法必须执行在读写事务上下文中。事务传播行为需要建立新的(REQUIRE一NEWS事务上下文中,事务隔离级别要求可重复读。在更新操作中一旦程序有异常,就需要事务进行回滚,回滚到更新操作执行之前的状态。
使用XML方式元数据的声明式配置,仅仅需要做如下配置
<bean id="messageService" class="com.messmgr. service.impl.MessageServiceImpl" />
<!-- 事务的传播特性 -->
<tx:advice id="txAdvice" transaction-manager= "tx-Manager">
<tx:attributes>
<tx:method name="get*" read-only="true" />
<tx:method name="*" propagation=" REQUIRES_NEW" isolation="REPEATABLE_READ" timeout="1" rollback-for="UnsupportedOperationException" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="messServiceOper" expression= "execution(*com.messmgr.service.impl.MessageServiceImpl *(..))"/>
<aop:advisor advice-ref="txAdbice" pointcut-ref=" messServiceOper" />
</aop:config>
上面的配置将为由’messageService’定义的bean创建一个代理对象,这个代理对象被装配了事务增强,所以当它的相应方法被调用时,一个事务将被启动、挂起、被标记为只读,或者其它(根据该方法所配置的事务语义)。
要把一个服务对象('messageService' bean)做成事务性的。将需要施加的事务语义封装在<tx: advice/>定义中。上例<tx:advice/>把所有以’get’开头的方法看做执行在只读事务上下文中,其余的方法执行在默认语义的事务上下文中,指定应用程序的Exception类型来标识事务回滚,propagation用来设置事务的传播行为。isolation用来设置事务的隔离级别,timeout事务超时的时间,read-only设定事务是否只读。
配置中最后一段是<aop: config/>的定义,它确保由’txAdvice’ bean定义的事务通知在应用中合适的点被执行。首先定义了一个切面,它匹配MessageServiceImpl定义的所有操作,我们把该切面叫做’messageServiceOperation’。然后我们用一个增强器(advisor)把这个切面与’txAdvice’绑定在一起,表示当’messageServiceOperation’执行时,’txAdvice’定义的增强逻辑将被执行。
4.2 AOP在事务处理中的优势
利用AOP机制,我们成功地将通过编码实现事务管理转型为通过声明实现事务管理。 AOP方案将预先定义好的事务处理模板,自动映射到需要进行事务处理的业务逻辑中。显著提高了开发效率,改善了编码质量,并降低了项目开发成本与风险。
5 结束语
综上所述,通过比较两种事务管理的应用,我们看到利用AOP实现事务管理,将事务管理从硬性的代码编写中转移到了配置信急的组合中,这样,使得业务逻辑代码变得更加简洁、清晰;而且如果要更换其它类型的事务管理器只需要对配置文件进行修改,所以事务管理更加灵活。
参考文献
[1]胡坚.一种基于AOP技术的工作流异常处理策略[J].微计算机信息,2005,11
[2]陈景燕.AOP下的权限控制实现[J].计算机与信急技术,2006,6
[3]陈雷,孟博.基于AOP技术的重构方法研究与实现[J].小型微型计算机系统,2004,9
[4]张广红,陈平.关于AOP实现机制和应用的研究[J].计算机工程于设计,2003,8
收稿日期:11 月 6 日 修改日期:1 月 10 日
作者简介:索丙芮(1984-),男,硕士研究生,研究方向:数据库。
|