共计 5984 个字符,预计需要花费 15 分钟才能阅读完成。
转自:http://www.linkedkeeper.com/d…
<!– more –>
声明式事务使用
Spring 事务是我们日常工作中经常使用的一项技术,Spring 提供了编程、注解、aop 切面三种方式供我们使用 Spring 事务,其中编程式事务因为对代码入侵较大所以不被推荐使用,注解和 aop 切面的方式可以基于需求自行选择,我们以注解的方式为例来分析 Spring 事务的原理和源码实现。
首先我们简单看一下 Spring 事务的使用方式,配置:
<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
在需要开启事务的方法上加上 @Transactional 注解即可,这里需要注意的是,当 <tx:annotation-driven/> 标签在不指定 transaction-manager 属性的时候,会默认寻找 id 固定名为 transactionManager 的 bean 作为事务管理器,如果没有 id 为 transactionManager 的 bean 并且在使用 @Transactional 注解时也没有指定 value(事务管理器),程序就会报错。当我们在配置两个以上的 <tx:annotation-driven/> 标签时,如下:
<tx:annotation-driven transaction-manager="transactionManager1"/>
<bean id="transactionManager1"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource1"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager2"/>
<bean id="transactionManager2"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource2"/>
</bean>
这时第一个 <tx:annotation-driven/> 会生效,也就是当我们使用 @Transactional 注解时不指定事务管理器,默认使用的事务管理器是 transactionManager1,后文分析源码时会具体提到这些注意点。
下面我们开始分析 Spring 的相关源码,首先看一下对 <tx:annotation-driven/> 标签的解析,这里需要读者对 Spring 自定义标签解析的过程有一定的了解,笔者后续也会出相关的文章。锁定 TxNamespaceHandler:
TxNamespaceHandler
(右键可查看大图)
注册事务功能 bean
这个方法比较长,关键的部分做了标记,最外围的 if 判断限制了 <tx:annotation-driven/> 标签只能被解析一次,所以只有第一次被解析的标签会生效。蓝色框的部分分别注册了三个 BeanDefinition,分别为 AnnotationTransactionAttributeSource、TransactionInterceptor 和 BeanFactoryTransactionAttributeSourceAdvisor,并将前两个 BeanDefinition 添加到第三个 BeanDefinition 的属性当中,这三个 bean 支撑了整个事务功能,后面会详细说明。我们先来看红色框的第个方法:
还记得当 <tx:annotation-driven/> 标签在不指定 transaction-manager 属性的时候,会默认寻找 id 固定名为 transactionManager 的 bean 作为事务管理器这个注意事项么,就是在这里实现的。下面我们来看红色框的第二个方法:
这两个方法的主要目的是注册 InfrastructureAdvisorAutoProxyCreator,注册这个类的目的是什么呢?我们看下这个类的层次:
使用 bean 的后处理方法获取增强器
我们发现这个类间接实现了 BeanPostProcessor 接口,我们知道,Spring 会保证所有 bean 在实例化的时候都会调用其 postProcessAfterInitialization 方法,我们可以使用这个方法包装和改变 bean,而真正实现这个方法是在其父类 AbstractAutoProxyCreator 类中:
上面这个方法相信大家已经看出了它的目的,先找出所有对应 Advisor 的类的 beanName,再通过 beanFactory.getBean 方法获取这些 bean 并返回。不知道大家还是否记得在文章开始的时候提到的三个类,其中 BeanFactoryTransactionAttributeSourceAdvisor 实现了 Advisor 接口,所以这个 bean 就会在此被提取出来,而另外两个 bean 被织入了 BeanFactoryTransactionAttributeSourceAdvisor 当中,所以也会一起被提取出来,下图为 BeanFactoryTransactionAttributeSourceAdvisor 类的层次:
Spring 获取匹配的增强器
下面让我们来看 Spring 如何在所有候选的增强器中获取匹配的增强器:
上面的方法中提到引介增强的概念,在此做简要说明,引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现该接口的代理,使用方法可以参考文末的引用链接。另外这个方法用两个重载的 canApply 方法为目标类寻找匹配的增强器,其中第一个 canApply 方法会调用第二个 canApply 方法并将第三个参数传为 false:
在上面 BeanFactoryTransactionAttributeSourceAdvisor 类的层次中我们看到它实现了 PointcutAdvisor 接口,所以会调用红框中的 canApply 方法进行判断,第一个参数 pca.getPointcut() 也就是调用 BeanFactoryTransactionAttributeSourceAdvisor 的 getPointcut 方法:
这里的 transactionAttributeSource 也就是我们在文章开始看到的为 BeanFactoryTransactionAttributeSourceAdvisor 织入的两个 bean 中的 AnnotationTransactionAttributeSource,我们以 TransactionAttributeSourcePointcut 作为第一个参数继续跟踪 canApply 方法:
我们跟踪 pc.getMethodMatcher() 方法也就是 TransactionAttributeSourcePointcut 的 getMethodMatcher 方法是在它的父类中实现:
发现方法直接返回 this,也就是下面 methodMatcher.matches 方法就是调用 TransactionAttributeSourcePointcut 的 matches 方法:
在上面我们看到其实这个 tas 就是 AnnotationTransactionAttributeSource,这里的目的其实也就是判断我们的业务方法或者类上是否有 @Transactional 注解,跟踪 AnnotationTransactionAttributeSource 的 getTransactionAttribute 方法:
方法中的事务声明优先级最高,如果方法上没有声明则在类上寻找:
this.annotationParsers 是在 AnnotationTransactionAttributeSource 类初始化的时候初始化的:
所以 annotationParser.parseTransactionAnnotation 就是调用 SpringTransactionAnnotationParser 的 parseTransactionAnnotation 方法:
至此,我们终于看到的 Transactional 注解,下面无疑就是解析注解当中声明的属性了:
Transactional 注解
在这个方法中我们看到了在 Transactional 注解中声明的各种常用或者不常用的属性的解析,至此,事务的初始化工作算是完成了,下面开始真正的进入执行阶段。
在上文 AbstractAutoProxyCreator 类的 wrapIfNecessary 方法中,获取到目标 bean 匹配的增强器之后,会为 bean 创建代理,这部分内容我们会在 Spring AOP 的文章中进行详细说明,在此简要说明方便大家理解,在执行代理类的目标方法时,会调用 Advisor 的 getAdvice 获取 MethodInterceptor 并执行其 invoke 方法,而我们本文的主角 BeanFactoryTransactionAttributeSourceAdvisor 的 getAdvice 方法会返回我们在文章开始看到的为其织入的另外一个 bean,也就是 TransactionInterceptor,它实现了 MethodInterceptor,所以我们分析其 invoke 方法:
这个方法很长,但是整体逻辑还是非常清晰的,首选获取事务属性,这里的 getTransactionAttrubuteSource() 方法的返回值同样是在文章开始我们看到的被织入到 TransactionInterceptor 中的 AnnotationTransactionAttributeSource,在事务准备阶段已经解析过事务属性并保存到缓存中,所以这里会直接从缓存中获取,接下来获取配置的 TransactionManager,也就是 determineTransactionManager 方法,这里如果配置没有指定 transaction-manager 并且也没有默认 id 名为 transactionManager 的 bean,就会报错,然后是针对声明式事务和编程式事务的不同处理,创建事务信息,执行目标方法,最后根据执行结果进行回滚或提交操作,我们先分析创建事务的过程。在分析之前希望大家能先去了解一下 Spring 的事务传播行为,有助于理解下面的源码,这里做一个简要的介绍,更详细的信息请大家自行查阅 Spring 官方文档,里面有更新详细的介绍。
Spring 的事务传播行为定义在 Propagation 这个枚举类中,一共有七种,分别为:
REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务,是默认的事务传播行为。
NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。
REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。
MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。
SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。
NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按 REQUIRED 属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对 DataSourceTransactionManager 事务管理器起效。
开启事务过程
判断当前线程是否存在事务就是判断记录的数据库连接是否为空并且 transactionActive 状态为 true。
REQUIRESNEW 会开启一个新事务并挂起原事务,当然开启一个新事务就需要一个新的数据库连接:
suspend 挂起操作主要目的是将当前 connectionHolder 置为 null,保存原有事务信息,以便于后续恢复原有事务,并将当前正在进行的事务信息进行重置。下面我们看 Spring 如何开启一个新事务:
这里我们看到了数据库连接的获取,如果是新事务需要获取新一个新的数据库连接,并为其设置了隔离级别、是否只读等属性,下面就是将事务信息记录到当前线程中:
接下来就是记录事务状态并返回事务信息:
然后就是我们目标业务方法的执行了,根据执行结果的不同做提交或回滚操作,我们先看一下回滚操作:
其中回滚条件默认为 RuntimeException 或 Error,我们也可以自行配置。
保存点一般用于嵌入式事务,内嵌事务的回滚不会引起外部事务的回滚。下面我们来看新事务的回滚:
很简单,就是获取当前线程的数据库连接并调用其 rollback 方法进行回滚,使用的是底层数据库连接提供的 API。最后还有一个清理和恢复挂起事务的操作:
如果事务执行前有事务挂起,那么当前事务执行结束后需要将挂起的事务恢复,挂起事务时保存了原事务信息,重置了当前事务信息,所以恢复操作就是将当前的事务信息设置为之前保存的原事务信息。到这里事务的回滚操作就结束了,下面让我们来看事务的提交操作:
在上文分析回滚流程中我们提到了如果当前事务不是独立的事务,也没有保存点,在回滚的时候只是设置一个回滚标记,由外部事务提交时统一进行整体事务的回滚。
提交操作也是很简单的调用数据库连接底层 API 的 commit 方法。
参考链接:
http://blog.163.com/asd_wll/b…
https://docs.spring.io/spring…