大家好,从这篇博客开始我们分析下Spring的事务模块。Spring事务的其中一个优点是非入侵式的编码,那么非入侵是怎么实现的呢?其实就是通过Spring Aop实现的。关于AOP我之前写过两篇博客,大家最好先看下,对于理解本文有帮助。
下面是一个Spring事务的例子,在之前分析AOP的博客中我也举过一个例子,并且详细分析了每个Bean对象的创建过程。所以下面这个例子关于Bean的创建我只是简单把几个关键点写一下。
一. 创建对象
<!-- 数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"> ... </bean> <!-- 配置局部事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 配置一个业务逻辑Bean --> <bean id="newsDao" class="testtx.tx.org.crazyit.app.dao.impl.NewsDaoImpl"> <property name="ds" ref="dataSource"/> </bean> <!-- 配置事务增强处理,指定事务管理器 --> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="get*" read-only="true"/> <tx:method name="*"/> </tx:attributes> </tx:advice> <aop:config> <!-- 定义切入点 --> <aop:pointcut id="myPointcut" expression="execution(* testtx.tx.org.crazyit.app.dao.impl.*Impl.*(..))"/> <!-- 定义通知器 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/> </aop:config>
在这个例子中一共有7个Bean对象需要创建,它们的ID如下(为了便于描述我对ID进行了简化处理),
1. dataSource
2. transactionManager
3. newsDao
4. txAdvice
5. internalAutoProxyCreator (用于创建代理对象的bean后处理器,该对象是Spring检测到存在aop配置自动添加的)
6. myPointcut (切入点)
7. DefaultBeanFactoryPointcutAdvisor#0 (通知器)
首先在初始化阶段,Spring发现有AOP相关配置,于是会自动注册一个Bean后处理器“internalAutoProxyCreator”,类型是AspectJAwareAdvisorAutoProxyCreator。
然后创建dataSource,在实例化dataSource之前会进入AspectJAwareAdvisorAutoProxyCreator的postProcessBeforeInstantiation方法,在这个方法中创建所有通知器,本例中只有一个通知器“DefaultBeanFactoryPointcutAdvisor#0”,进入填充对象属性值时,会给它的两个属性“adviceBeanName”和“pointcut”赋值,值分别是“txAdvice(字符串)”和“AspectJExpressionPointcut(就是myPointcut的实例,是个property bean)”。
然后继续创建dataSource对象,在完成了创建dataSource对象的初始化步骤后,会调用Bean后处理器的postProcessAfterInitialization方法,而上述AspectJAwareAdvisorAutoProxyCreator就是一个Bean后处理器,于是在它的postProcessAfterInitialization方法中会判断是否有必要为dataSource创建代理对象,判断是由通知器的切入点对象完成的,切入点对象持有aspectj表达式,本例中是“* testtx.tx.org.crazyit.app.dao.impl.*Impl.*(..)”,显然无需为dataSource创建代理对象,至此dataSource创建完成。
然后创建transactionManager对象,它的过程跟dataSource是相似的,无非通知器此时已创建,然后transactionManager也不在被代理的范围之内。再接着创建newsDao,重点来了,newDao是匹配aspectj表达式的,需要为他创建代理对象,此时会触发创建通知对象"txAdvice",它的类型是TransactionInterceptor,就是一个Aop大联盟的方法拦截器,Spring的通知对象即是拦截器,这个观点我之前也表述过。在填充txtAdvice属性值阶段,会为它的两个属性“transactionManager”和“transactionAttributeSource”赋值,前者的值就是transactionAttributeSource,该对象已存在;后者的值是NameMatchTransactionAttributeSource,这个对象的作用是获取事务属性对象,这是一个单例Bean,需要创建,当进入填充该对象的属性值阶段,会为它属性nameMap赋值,它的值就是用户配置的事务属性,key是方法名称,value是事务属性对象,可见不同的方法对应不同的事务配置。
{get*=PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly, *=PROPAGATION_REQUIRED,ISOLATION_DEFAULT}。
我之前的博客说过,通知定义的顺序不一定是执行顺序,假设事务通知,前置通知,后置通知,最终通知,异常后通知都存在的情况,通知的执行顺序如下,
- TransactionInterceptor (事务通知)
- MethodBeforeAdviceInterceptor (前置通知)
- AspectJAfterThrowingAdvice (异常后通知)
- AspectJAfterAdvice (最终通知)
- AfterReturningAdviceInterceptor (后置通知)
通过上述的过程,就完成了创建newsDao的代理对象,调用该类的所有方法都会被拦截,从而织入事务控制逻辑。
二. 简述Spring事务
所谓Spring事务其实就是ORM框架把事务管理这部分工作委托给Spring来完成,自己不再处理事务相关操作,用张图来描述下,图中用红色字体表述的事务操作就是通过AOP织入的。
Spring事务管理是由事务管理器来负责的,所有的事务管理器都实现了PlatformTransactionManager接口,该接口中定义三个方法:(1). 获取事务对象;(2).提交事务;(3):回滚事务。其中比较常用的一个事务管理器是DataSourceTransactionManager,这里简单描述下。这是一个基于数据库连接对象的事务管理器,它持有单个数据库连接,通过连接实现事务提交和回滚,它只支持局部事务。当Dao层的类执行数据库操作时,会被TransactionInterceptor拦截,拦截后所做的核心工作就是要创建一个jdbc连接对象,把连接对象设置成手动提交,并把它保存到ThreadLocal变量中,目的是要确保执行CRUD时使用的是同一个连接对象,连接的存储和获取具体是通过TransactionSynchronizationManager对象实现的。当CRUD执行结束后,调用连接对象的commit()或rollback()方法。