spring源码_Spring源码剖析9:Spring事务源码剖析

0b16478ced18bcc9b217b82926dfc82c.png

转自:http://www.linkedkeeper.com/detail/blog.action?bid=1045

本系列文章将整理到我在GitHub上的《Java面试指南》仓库,更多精彩内容请到我的仓库里查看

https:// github.com/h2pl/Java-Tu torial

喜欢的话麻烦点下Star哈

文章将同步到我的个人博客:

http://www. how2playlife.com

本文是微信公众号【Java技术江湖】的《Spring和SpringMVC源码分析》其中一篇,本文部分内容来源于网络,为了把本文主题讲得清晰透彻,也整合了很多我认为不错的技术博客内容,引用其中了一些比较好的博客文章,如有侵权,请联系作者。

该系列博文会告诉你如何从spring基础入手,一步步地学习spring基础和springmvc的框架知识,并上手进行项目实战,spring框架是每一个Java工程师必须要学习和理解的知识点,进一步来说,你还需要掌握spring甚至是springmvc的源码以及实现原理,才能更完整地了解整个spring技术体系,形成自己的知识框架。

后续还会有springboot和springcloud的技术专题,陆续为大家带来,敬请期待。

为了更好地总结和检验你的学习成果,本系列文章也会提供部分知识点对应的面试题以及参考答案。

如果对本系列文章有什么建议,或者是有什么疑问的话,也可以关注公众号【Java技术江湖】联系作者,欢迎你参与本系列博文的创作和修订。

声明式事务使用

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注解即可,这里需要注意的是,当
标签在不指定transaction-manager属性的时候,会默认寻找id固定名为transactionManager的bean作为事务管理器,如果没有id为transactionManager的bean并且在使用@Transactional注解时也没有指定value(事务管理器),程序就会报错。当我们在配置两个以上的
标签时,如下:

    <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>

这时第一个
会生效,也就是当我们使用@Transactional注解时不指定事务管理器,默认使用的事务管理器是transactionManager1,后文分析源码时会具体提到这些注意点。

下面我们开始分析Spring的相关源码,首先看一下对
标签的解析,这里需要读者对Spring自定义标签解析的过程有一定的了解,笔者后续也会出相关的文章。锁定TxNamespaceHandler:

TxNamespaceHandler

(右键可查看大图)

1120a0eb8c7c90c51e6048dba663f597.png

b76c95f6c0c0fd071eae290632f61b57.png

752186ce9da2e2d3d165a52f5eac729b.png

注册事务功能bean

这个方法比较长,关键的部分做了标记,最外围的if判断限制了
标签只能被解析一次,所以只有第一次被解析的标签会生效。蓝色框的部分分别注册了三个BeanDefinition,分别为AnnotationTransactionAttributeSource、TransactionInterceptor和BeanFactoryTransactionAttributeSourceAdvisor,并将前两个BeanDefinition添加到第三个BeanDefinition的属性当中,这三个bean支撑了整个事务功能,后面会详细说明。我们先来看红色框的第个方法:

8a12372931ca50c13a74a6c52a718c23.png

8dc34a19c0921519fd5ca0d142867884.png

还记得当
标签在不指定transaction-manager属性的时候,会默认寻找id固定名为transactionManager的bean作为事务管理器这个注意事项么,就是在这里实现的。下面我们来看红色框的第二个方法:

c8a3cf21400a231584b29c9bddd20472.png

72fcd210c7319e1f4f30933bdacaafa3.png

这两个方法的主要目的是注册InfrastructureAdvisorAutoProxyCreator,注册这个类的目的是什么呢?我们看下这个类的层次:

feceadafee8575e590a0058944b728bc.png

使用bean的后处理方法获取增强器

我们发现这个类间接实现了BeanPostProcessor接口,我们知道,Spring会保证所有bean在实例化的时候都会调用其postProcessAfterInitialization方法,我们可以使用这个方法包装和改变bean,而真正实现这个方法是在其父类AbstractAutoProxyCreator类中:

67ba1d9d7e8baa5e669efbe0c97e3e06.png

df5f3aa4f109fa2b3f20219a3edaa9d0.png

283b17806abe664a0a7d0e9037ef7fec.png

f268b712b7b000e090b1c0880b6c2187.png

a69bc2c66a1d30b1c9175211f0da6646.png

84efaf85ee48ddd72e72901493a00d5a.png

上面这个方法相信大家已经看出了它的目的,先找出所有对应Advisor的类的beanName,再通过beanFactory.getBean方法获取这些bean并返回。不知道大家还是否记得在文章开始的时候提到的三个类,其中BeanFactoryTransactionAttributeSourceAdvisor实现了Advisor接口,所以这个bean就会在此被提取出来,而另外两个bean被织入了BeanFactoryTransactionAttributeSourceAdvisor当中,所以也会一起被提取出来,下图为BeanFactoryTransactionAttributeSourceAdvisor类的层次:

6771d6bf357fe3cbb47cb39fa9758915.png

Spring获取匹配的增强器

下面让我们来看Spring如何在所有候选的增强器中获取匹配的增强器:

7180cd2777deab3ee5ae144f24ae06df.png

5b618b7fa834000770123360367776f9.png

上面的方法中提到引介增强的概念,在此做简要说明,引介增强是一种比较特殊的增强类型,它不是在目标方法周围织入增强,而是为目标类创建新的方法和属性,所以引介增强的连接点是类级别的,而非方法级别的。通过引介增强,我们可以为目标类添加一个接口的实现,即原来目标类未实现某个接口,通过引介增强可以为目标类创建实现该接口的代理,使用方法可以参考文末的引用链接。另外这个方法用两个重载的canApply方法为目标类寻找匹配的增强器,其中第一个canApply方法会调用第二个canApply方法并将第三个参数传为false:

862980b2d5a19d81964bc44d152c56a4.png

在上面BeanFactoryTransactionAttributeSourceAdvisor类的层次中我们看到它实现了PointcutAdvisor接口,所以会调用红框中的canApply方法进行判断,第一个参数pca.getPointcut()也就是调用BeanFactoryTransactionAttributeSourceAdvisor的getPointcut方法:

75e133409cc8de79b37a814c6b056ad1.png

这里的transactionAttributeSource也就是我们在文章开始看到的为BeanFactoryTransactionAttributeSourceAdvisor织入的两个bean中的AnnotationTransactionAttributeSource,我们以TransactionAttributeSourcePointcut作为第一个参数继续跟踪canApply方法:

6cb7fa25fc68cf55bd7aeec7203cc8c4.png

我们跟踪pc.getMethodMatcher()方法也就是TransactionAttributeSourcePointcut的getMethodMatcher方法是在它的父类中实现:

019cb83f7a83a82b6ce741d3712ccfa8.png

发现方法直接返回this,也就是下面methodMatcher.matches方法就是调用TransactionAttributeSourcePointcut的matches方法:

9bb51c06c4287c42ca3a64afadacf306.png

在上面我们看到其实这个tas就是AnnotationTransactionAttributeSource,这里的目的其实也就是判断我们的业务方法或者类上是否有@Transactional注解,跟踪AnnotationTransactionAttributeSource的getTransactionAttribute方法:

dcb703fee2756f1aaf11c4504e473f47.png

5f5907fec3aba5c2675dbdb4ed863d2a.png

方法中的事务声明优先级最高,如果方法上没有声明则在类上寻找:

8f5bfe02c988332f26f30381ba41758b.png

751a68b91ffcc4a5f895ec920b9d653f.png

this.annotationParsers是在AnnotationTransactionAttributeSource类初始化的时候初始化的:

41e9243f300d1e96974845e026cc7966.png

所以annotationParser.parseTransactionAnnotation就是调用SpringTransactionAnnotationParser的parseTransactionAnnotation方法:

47145d7a75729f20244602fbb15aa165.png

至此,我们终于看到的Transactional注解,下面无疑就是解析注解当中声明的属性了:

Transactional注解

316ada49938cb6ac0ced9bcd28d8d9eb.png

在这个方法中我们看到了在Transactional注解中声明的各种常用或者不常用的属性的解析,至此,事务的初始化工作算是完成了,下面开始真正的进入执行阶段。

在上文AbstractAutoProxyCreator类的wrapIfNecessary方法中,获取到目标bean匹配的增强器之后,会为bean创建代理,这部分内容我们会在Spring AOP的文章中进行详细说明,在此简要说明方便大家理解,在执行代理类的目标方法时,会调用Advisor的getAdvice获取MethodInterceptor并执行其invoke方法,而我们本文的主角BeanFactoryTransactionAttributeSourceAdvisor的getAdvice方法会返回我们在文章开始看到的为其织入的另外一个bean,也就是TransactionInterceptor,它实现了MethodInterceptor,所以我们分析其invoke方法:

f304c77ded896e3198ca8b46870873ea.png

c9475b7310f3601d06eceffad66c08b8.png

这个方法很长,但是整体逻辑还是非常清晰的,首选获取事务属性,这里的getTransactionAttrubuteSource()方法的返回值同样是在文章开始我们看到的被织入到TransactionInterceptor中的AnnotationTransactionAttributeSource,在事务准备阶段已经解析过事务属性并保存到缓存中,所以这里会直接从缓存中获取,接下来获取配置的TransactionManager,也就是determineTransactionManager方法,这里如果配置没有指定transaction-manager并且也没有默认id名为transactionManager的bean,就会报错,然后是针对声明式事务和编程式事务的不同处理,创建事务信息,执行目标方法,最后根据执行结果进行回滚或提交操作,我们先分析创建事务的过程。在分析之前希望大家能先去了解一下Spring的事务传播行为,有助于理解下面的源码,这里做一个简要的介绍,更详细的信息请大家自行查阅Spring官方文档,里面有更新详细的介绍。

Spring的事务传播行为定义在Propagation这个枚举类中,一共有七种,分别为:

REQUIRED:业务方法需要在一个容器里运行。如果方法运行时,已经处在一个事务中,那么加入到这个事务,否则自己新建一个新的事务,是默认的事务传播行为。

NOT_SUPPORTED:声明方法不需要事务。如果方法没有关联到一个事务,容器不会为他开启事务,如果方法在一个事务中被调用,该事务会被挂起,调用结束后,原先的事务会恢复执行。

REQUIRESNEW:不管是否存在事务,该方法总汇为自己发起一个新的事务。如果方法已经运行在一个事务中,则原有事务挂起,新的事务被创建。

MANDATORY:该方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果在没有事务的环境下被调用,容器抛出例外。

SUPPORTS:该方法在某个事务范围内被调用,则方法成为该事务的一部分。如果方法在该事务范围外被调用,该方法就在没有事务的环境下执行。

NEVER:该方法绝对不能在事务范围内执行。如果在就抛例外。只有该方法没有关联到任何事务,才正常执行。

NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行。它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点。内部事务的回滚不会对外部事务造成影响。它只对DataSourceTransactionManager事务管理器起效。

开启事务过程

307f2733862cb5fcf15880cb401ede83.png

1ff76a162a7870253b15b7cc0900f6fe.png

7067618c7657efb2fb8f0c1838245fc6.png

判断当前线程是否存在事务就是判断记录的数据库连接是否为空并且transactionActive状态为true。

d662ded411cd580a08f03144d5a4848f.png

09a9f201e9374cec46a723ef579b2194.png

REQUIRESNEW会开启一个新事务并挂起原事务,当然开启一个新事务就需要一个新的数据库连接:

5cdb963d039cdcb4edf754e522bd2069.png

68ae6c5795f34adadcf0584a75f467de.png

suspend挂起操作主要目的是将当前connectionHolder置为null,保存原有事务信息,以便于后续恢复原有事务,并将当前正在进行的事务信息进行重置。下面我们看Spring如何开启一个新事务:

9db163630bb5440273a41c08ac9f51c5.png

这里我们看到了数据库连接的获取,如果是新事务需要获取新一个新的数据库连接,并为其设置了隔离级别、是否只读等属性,下面就是将事务信息记录到当前线程中:

7b88d217c51a44cb5f76b3241e473469.png

接下来就是记录事务状态并返回事务信息:

4aff82ba1b630fc9263b0e3f1c9b0e6a.png

然后就是我们目标业务方法的执行了,根据执行结果的不同做提交或回滚操作,我们先看一下回滚操作:

9faf91c4295b331486f14dcaecfcf735.png

dd06e7d2fd64692e3bc456e73865331b.png

其中回滚条件默认为RuntimeException或Error,我们也可以自行配置。

767dc8e537670da85b8f98e2cf2c45c9.png

58ee0a9d88a4d9b809317389a68ccc7a.png

e408cd8d1c7f4cb97f508986cb4ace33.png

2614b68e84bf6556b83a4b9f6b9b7b22.png

保存点一般用于嵌入式事务,内嵌事务的回滚不会引起外部事务的回滚。下面我们来看新事务的回滚:

21cf62918f8fab50b48056e60b322573.png

很简单,就是获取当前线程的数据库连接并调用其rollback方法进行回滚,使用的是底层数据库连接提供的API。最后还有一个清理和恢复挂起事务的操作:

ace61a23ddfc17f0c29ee9dc26482f1f.png

80bf7f148e719960e2b6b4a4c1576616.png

3d7de36d10724d2af12c3ba443b3456c.png

如果事务执行前有事务挂起,那么当前事务执行结束后需要将挂起的事务恢复,挂起事务时保存了原事务信息,重置了当前事务信息,所以恢复操作就是将当前的事务信息设置为之前保存的原事务信息。到这里事务的回滚操作就结束了,下面让我们来看事务的提交操作:

a570e427ae5655f3707d44c208c67014.png

6f9e0c8ee9017709929a27a78eea1de3.png

在上文分析回滚流程中我们提到了如果当前事务不是独立的事务,也没有保存点,在回滚的时候只是设置一个回滚标记,由外部事务提交时统一进行整体事务的回滚。

a0031492a03de6a69286bd20046b117e.png

4ff8c451a815a8c443f29adfc5a98624.png

提交操作也是很简单的调用数据库连接底层API的commit方法。

参考链接:

http://blog.163.com/asd_wll/blog/static/2103104020124801348674/

https://docs.spring.io/spring/docs/current/spring-framework-reference/data-access.html#spring-data-tier

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值