觉得不错请按下图操作,掘友们,哈哈哈!!!
一:概述及目录
这个是源码系列的第 4 篇,感觉不错的小伙伴请一键三连哦!!!
这篇源码解析,和 Spring AOP 中的知识有很多重合的地⽅,但是⽐ AOP 要稍微简单⼀些,建议两篇⽂章对⽐学 习。
下⾯我会简单介绍⼀下 Spring 事务的基础知识,以及使⽤⽅法,然后直接对源码进⾏拆解。
目录:
二. 项⽬准备
下⾯是 DB 数据和 DB 操作接⼝:
Id | uname | usex |
---|---|---|
1 | 小王 | 男 |
2 | 小李 | 女 |
1 | 小赵 | 男 |
1 | arduino复制代码@Data |
1 | sql复制代码public interface UserDao { |
基础测试代码,testSuccess() 是事务⽣效的情况:
1 | java复制代码@Service |
执⾏⼊⼝:
1 | ini复制代码public class SpringMyBatisTest { |
输出:
三:Spring 事务⼯作流程
为了⽅便⼤家能更好看懂后⾯的源码,我先整体介绍⼀下源码的执⾏流程,让⼤家有⼀个整体的认识,否则容易被 绕进去。
整个 Spring 事务源码,其实分为 2 块,我们会结合上⾯的示例,给⼤家进⾏讲解。
第⼀块是后置处理,我们在创建 Model Bean 的后置处理器中,⾥⾯会做两件事情:
获取 Model 的切⾯⽅法:⾸先会拿到所有的切⾯信息,和 Model 的所有⽅法进⾏匹配,然后找到 Model 所有需 要进⾏事务处理的⽅法,匹配成功的⽅法,还需要将事务属性保存到缓存 attributeCache 中。
创建 AOP 代理对象:结合 Model 需要进⾏ AOP 的⽅法,选择 Cglib 或 JDK,创建 AOP 代理对象。
第⼆块是事务执⾏,整个逻辑⽐较复杂,我只选取 4 块最核⼼的逻辑,分别为从缓存拿到事务属性、创建并开启事 务、执⾏业务逻辑、提交或者回滚事务。
四. 源码解读
注意:Spring 的版本是 5.2.15.RELEASE,否则和我的代码不⼀样!!!
上⾯的知识都不难,下⾯才是我们的重头戏,让我们一起⾛⼀遍代码流程。
4.1 代码⼊⼝
这⾥需要多跑⼏次,把前⾯的 beanName 跳过去,只看 model。
进⼊ doGetBean(),进⼊创建 Bean 的逻辑。
进⼊ createBean(),调⽤ doCreateBean()。
进⼊ doCreateBean(),调⽤ initializeBean()。
如果看过我前⾯⼏期系列源码的同学,对这个⼊⼝应该会⾮常熟悉,其实就是⽤来创建代理对象。
4.2 创建代理对象
这⾥是重点!敲⿊板!!!
- 先获取 model 类的所有切⾯列表;
- 创建⼀个 AOP 的代理对象。
4.2.1 获取切⾯列表
这⾥有 2 个重要的⽅法,先执⾏ findCandidateAdvisors(),待会我们还会再返回 findEligibleAdvisors()。
依次返回,重新来到 findEligibleAdvisors()。
进⼊ canApply(),开始匹配 model 的切⾯。
这⾥是重点!敲⿊板!!!
这⾥只会匹配到 Model.testSuccess() ⽅法,我们直接进⼊匹配逻辑。
如果匹配成功,还会把事务的属性配置信息放⼊ attributeCache 缓存。
我们依次返回到 getTransactionAttribute(),再看看放⼊缓存中的数据。
再回到该⼩节开头,我们拿到 mdoel 的切⾯信息,去创建 AOP 代理对象。
4.2.2 创建 AOP 代理对象
创建 AOP 代理对象的逻辑,在上⼀篇⽂章【Spring源码解析-Spring AOP】讲解过,我是通过 Cglib 创建,感兴趣的同学可以翻⼀下我的历史⽂章。
4.3 事务执⾏
回到业务逻辑,通过 model 的 AOP 代理对象,开始执⾏主⽅法。
因为代理对象是 Cglib ⽅式创建,所以通过 Cglib 来执⾏。
这⾥是重点!敲⿊板!!!
下⾯的代码是事务执⾏的核⼼逻辑 invokeWithinTransaction()。
1 | java复制代码protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, |
4.3.1 获取事务属性
在 invokeWithinTransaction() 中,我们找到获取事务属性的⼊⼝。
从 attributeCache 获取事务的缓存数据,缓存数据是在 “3.2.1 获取切⾯列表” 中保存的。
4.3.2 创建事务
通过 doGetTransaction() 获取事务。
1 | scss复制代码protected Object doGetTransaction() { |
通过 startTransaction() 开启事务。
下⾯是开启事务的详细逻辑,了解⼀下即可。
1 | scss复制代码protected void doBegin(Object transaction, TransactionDefinition definition) { |
最后返回到 invokeWithinTransaction(),得到 txInfo 对象。
4.3.3 执⾏逻辑
还是在 invokeWithinTransaction() 中,开始执⾏业务逻辑。
进⼊到真正的业务逻辑。
执⾏完毕后抛出异常,依次返回,⾛后续的回滚事务逻辑。
4.3.4 回滚事务
还是在 invokeWithinTransaction() 中,进⼊回滚事务的逻辑。
执⾏回滚逻辑很简单,我们只看如何判断是否回滚。
如果抛出的异常类型,和事务定义的异常类型匹配,证明该异常需要捕获。
之所以⽤递归,不仅需要判断抛出异常的本身,还需要判断它继承的⽗类异常,满⾜任意⼀个即可捕获。
到这⾥,所有的流程结束。
五. 总要有总结
我们再⼩节⼀下,⽂章先介绍了事务的使⽤示例,以及事务的执⾏流程。
之后再剖析了事务的源码,分为 2 块:
- 先匹配出 model 对象所有关于事务的切⾯列表,并将匹配成功的事务属性保存到缓存;
- 从缓存取出事务属性,然后创建、启动事务,执⾏业务逻辑,最后提交或者回滚事务。
这篇⽂章,是 Spring 源码解析的第 4 篇,如果之前已经看过 AOP 的源码解析,这篇理解起来就容易很多,但是如果上来 就直接肝,可能会有一丢丢难度哦。
本文正在参加「金石计划」
本文转载自: 掘金