Spring 事务原理二

该说些什么呢?一连几天,我都沉溺在孤芳自赏的思维中无法自拔。不知道自己为什么会有这种令人不齿的表现,更不知道这颗定时炸弹何时会将人炸的粉身碎骨。好在儒派宗师曾老夫子“吾日三省吾身”的名言警醒了我。遂潜心自省,溯源头以解迷思:一开始,我的目标就是梳理知识。但最近博君一笑的怪异思维让我每日如跳梁小丑般为博君一笑而胡乱行文,后又因觉背离宗旨而主动删除。虽尚未因此遭受处罚,但这种背离宗旨的行径,着实令人不齿。为回归本心,我决定继续前一篇博文所述主题。

上一篇博客我们梳理了与事务相关的基本概念及Spring事务的基本用法,本篇博客我们将探究其基本原理。有心人可能已经发现了,其实Spring事务的实现方式与前面梳理的AOP的实现方式类似:将事务增强方法与目标方法组成一个执行链,然后由调度者依次调度执行链中的相关方法,从而达到事务控制的目标。下面就让我们从注解@ EnableTransactionManagement开始,然后一步一步向下探索吧。

该注解上面有这样一句@Import(TransactionManagementConfigurationSelector.class),其中的TransactionManagementConfigurationSelector类继承了AdviceModeImportSelector类。该类会通过selectImports(AdviceMode)方法导入两个组件ProxyTransactionManagementConfiguration和AutoProxyRegistrar。其中后者向Spring容器中注册一个InfrastructureAdvisorAutoProxyCreator 组件,该组件会通过后置处理器在对象创建以后,包装对象,然后返回一个代理对象(包含增强器)——一个包含所有拦截器链的代理对象,执行该代理对象,本质上就是执行这个拦截器链前者向容器注册一个ProxyTransactionManagementConfiguration类型的配置对象,该对象会继续向容器中注入一个事务增强器,即BeanFactoryTransactionAttributeSourceAdvisor对象(该对象会包含一个TransactionAttributeSource类型的属性,该属性的作用是用于解析事务注解,即@Transactional,故其实际类型为AnnotationTransactionAttributeSource;另外该对象还会包含一个事务拦截器,即TransactionInterceptor,它保存了事务属性信息,比如事务管理器——TransactionManager和TransactionAttributeSource等)。

下面一起来看一下InfrastructureAdvisorAutoProxyCreator类。看到这个类是不是觉得很熟悉?是的,在《Spring AOP总结二》这篇博文中,我们梳理过一个相似类型的类——AnnotationAwareAspectJAutoProxyCreator(它也继承成了ProxyProcessorSupport类,同时实现了BeanFactoryAware、SmartInstantiationAwareBeanPostProcessor接口)。下面这幅图展示的是InfrastructureAdvisorAutoProxyCreator类的结构图:

通过这幅图我们不难发现InfrastructureAdvisorAutoProxyCreator类是BeanPostProcessor接口的一个实现类,所以其必定实现了BeanPostProcessor接口中定义的两个方法:

  1. postProcessBeforeInitialization(Object bean, String beanName)
  2. postProcessAfterInitialization(Object bean, String beanName)

不过需要注意的是这两个方法的真正实现体位于AbstractAutoProxyCreator类中,关于这两个方法的具体执行逻辑,这里就不再详细描述了,想了解详情可以参见前面关于AOP的系列文章。梳理这个类的目的只有一个,希望自己能够弄清楚这个类在Spring整个事务处理中的作用:通过后置处理器在需要事务的目标对象创建以后,对该对象进行包装,然后返回一个包含增强器的代理对象

下面我们一起看一下ProxyTransactionManagementConfiguration类。这个类的源码如下面所示:

@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
@ImportRuntimeHints(TransactionRuntimeHints.class)
public class ProxyTransactionManagementConfiguration extends AbstractTransactionManagementConfiguration {@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME)@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor(TransactionAttributeSource transactionAttributeSource, TransactionInterceptor transactionInterceptor) {BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor();advisor.setTransactionAttributeSource(transactionAttributeSource);advisor.setAdvice(transactionInterceptor);if (this.enableTx != null) {advisor.setOrder(this.enableTx.<Integer>getNumber("order"));}return advisor;}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionAttributeSource transactionAttributeSource() {// Accept protected @Transactional methods on CGLIB proxies, as of 6.0.return new AnnotationTransactionAttributeSource(false);}@Bean@Role(BeanDefinition.ROLE_INFRASTRUCTURE)public TransactionInterceptor transactionInterceptor(TransactionAttributeSource transactionAttributeSource) {TransactionInterceptor interceptor = new TransactionInterceptor();interceptor.setTransactionAttributeSource(transactionAttributeSource);if (this.txManager != null) {interceptor.setTransactionManager(this.txManager);}return interceptor;}}

通过源码可以发现,该类如前面所说的那样,是一个配置类。在这个类里面我们主要关注其第一个方法,该方法的主要作用是创建一个BeanFactoryTransactionAttributeSourceAdvisor对象,并将其注册到Spring容器中。该类的继承结构如下所示:

下面拿BeanFactoryTransactionAttributeSourceAdvisor这个类与前面AOP系列文章中的InstantiationModelAwarePointcutAdvisorImpl类进行对比。不难看出它们都实现了Advisor接口。因此,理论上讲,这两个类的作用是一样的。根据前面对AOP的跟踪个人理解InstantiationModelAwarePointcutAdvisorImpl类的主要作用是保存关键数据(比如持有一个AspectJExpressionPointcut对象),并依据持有的关键数据创建对应的Advice,比如AspectJAroundAdvice、AspectJMethodBeforeAdvice、AspectJAfterAdvice、AspectJAfterReturningAdvice、AspectJAfterThrowingAdvice,等等。而这里要讲的Advisor的实现类BeanFactoryTransactionAttributeSourceAdvisor的作用也类似——整合Pointcut及Advise以方便后期使用(其持有的Advice和Pointcut对象,均是显式创建的,即new方式,具体见ProxyTransactionManagementConfigurationBeanFactoryTransactionAttributeSourceAdvisor的源码)。该对象在容器中有两个调用入口,一个调用点位于AbstractAutoProxyCreator类的postProcessBeforeInitialization()方法中,另一个调用点位于本类的postProcessAfterInitialization()方法中。上篇文章中的示例,在获取容器中的TransferService对象时,触发了BeanFactoryTransactionAttributeSourceAdvisor对象的使用,详细参见下面图片:

图中最上面箭头所指的代码就是执行的入口,通过这个方法系统可以找到适用于当前对象的Advisor对象,比如这里的一直梳理的BeanFactoryTransactionAttributeSourceAdvisor对象。接下来就是创建代理对象了(这里的操作比较复杂,这里就不再赘述,如果有兴趣的可以翻阅一下《Spring AOP总结四》这篇文章)。

接下来,让我们一起继续看看BeanFactoryTransactionAttributeSourceAdvisor源码中的TransactionAttributeSourcePointcut类型的对象。TransactionAttributeSourcePointcut类的继承体系如下图所示:

为了加深理解,我们将其与Spring AOP中的AspectJExpressionPointcut的类结构图做个对比,下面这幅图就是AspectJExpressionPointcut的类结构图(注意下面这幅图中的IntroductionAwareMethodMatcher类继承了MethodMatcher接口,BeanFactoryAware接口则继承了Aware接口):

从这两幅图可以看出:TransactionAttributeSourcePointcut和AspectJExpressionPointcut都实现了Pointcut和MethodMatcher接口。因此它们是一个方法匹配器,其中定义了方法匹配规则,也是过滤器的一种实现,其主要用于判断哪些方法需要使用当前的增强业务。这里有个问题AspectJExpressionPointcut类继承了ClassFilter,但是TransactionAttributeSourcePointcut没有,那它只通过实现MethodMatcher接口就可以完成所有过滤功能吗?不是这样的 TransactionAttributeSourcePointcut类的构造方法中有这样一行代码,如下图所示:

通过这行代码TransactionAttributeSourcePointcut对象会持有一个类型TransactionAttributeSourceClassFilter的过滤器。TransactionAttributeSourceClassFilter类的继承体系如下图所示:

所以这里很清楚了TransactionAttributeSourcePointcut会通过持有一个外部ClassFilter的方式来引入一个限制切入点或引入点与给定目标类集的匹配的筛选器,目的是用于筛选那些类需要被处理,哪些类不需要被处理。因此TransactionAttributeSourceClassFilter是过滤器,其主要作用就是筛选出合适的类,而过滤掉不合适的类。

再回到ProxyTransactionManagementConfiguration源码的第一个方法中,在创建完BeanFactoryTransactionAttributeSourceAdvisor对象后,其后面紧跟了两行属性赋值代码,具体如下图所示:

这两行代码的主要作用是为BeanFactoryTransactionAttributeSourceAdvisor对象的advice及transactionAttributeSource两个属性进行初始化。其中adevice属性的的实际类型为TransactionInterceptor,transactionAttributeSource的实际类型为AnnotationTransactionAttributeSource注意setTransactionAttributeSource()方法的本质是将AnnotationTransactionAttributeSource对象赋值给BeanFactoryTransactionAttributeSourceAdvisor对象所持有的TransactionAttributeSourcePointcut对象。下面我们就来看一下这两个类的结构图:

  • TransactionInterceptor(注意图中除了MethodInterceptor和Interceptor位于org.aopalliance.intercept包中外,其他类均位于org.springframework包中)

梳理到这里我不禁想到了Spring AOP中常见的五个通知,详情可以浏览一下《Spring AOP总结二》这篇文章。这五个通知中有三个直接实现了MethodInterceptor接口,这三个接口分别为:AspectJAfterAdvice、AspectJAroundAdvice、AspectJAfterThrowingAdvice。另外两个虽然没有直接实现这个接口,但在Spring容器启动过程中会对他们进行包装,最终包装为两个实现了MethodInterceptor接口的对象,它们分别为:MethodBeforeAdviceInterceptor和AfterReturningAdviceInterceptor。具体可以看一下《Spring AOP总结四》这篇文章。通过实现MethodInterceptor接口,最终TransactionInterceptor类有了被调用的机会(因为该类实现了MethodInterceptor接口中的invoke(MethodInvocation)方法,该方法会被最终创建的代理对象持有的链中的DynamicAdvisedInterceptor对象通过CglibMethodInvocation对象中的proceed()进行调用,进而完成对事务的控制。这部分执行逻辑同样可以在《Spring AOP总结四》这篇文章中看到)

  • AnnotationTransactionAttributeSource

下面我们看一下这个类的isCandidateClass(Class<?>)方法,该方法会对传递进来的目标class进行判断,通过调用TransactionAnnotationParser类的isCandidateClass()进行判断,其实际调用点如下图所示:

图中的Pointcut的实际类型为TransactionAttributeSourcePointcut,这里的pc.getClassFilter()会获得TransactionAttributeSourcePointcut$TransactionAttributeSourceClassFilter类型的对象,然后调用该对象的matches(Class<?>)方法,具体如下图所示:

紧接着上图所示的代码进行判断后,会直接调用TransactionAttributeSource对象上的isCandidateClass(Class<?>)方法(实际走的是AnnotationTransactionAttributeSource),注意这个TransactionAttributeSource属性的初始化是在ProxyTransactionManagementConfiguration类创建BeanFactoryTransactionAttributeSourceAdvisor对象时完成的。具体如下图所示:

AnnotationTransactionAttributeSource#isCandidateClass(Class<?>)方法会遍历本类持有的TransactionAnnotationParser集合,然后调用其上的isCandidateClass(Class<?>)方法,注意这里的TransactionAnnotationParser集合只有一个数据,即SpringTransactionAnnotationParser,下面看一下这个类的结构图:

继续看SpringTransactionAnnotationParser类中的isCandidateClass(Class<?>)方法,其源码如下所示:

public boolean isCandidateClass(Class<?> targetClass) {return AnnotationUtils.isCandidateClass(targetClass, Transactional.class);
}

从这段代码可以看出,其主要作用就是判断目标类上是否有Transactional注解。具体如下图所示:

从这幅图可以看到前一节案例中TransferServiceImpl类(添加了@Transactional注解)在这里执行返回了true,因此这段代码的主要作用就是判断类上是否有@Transactional注解。接下来,我们继续看后面的逻辑,这就要回到下图所示的代码处了:

根据前面梳理的TransactionAttributeSourcePointcut的结构图可知,其继承了StaticMethodMatcherPointcut类,同时也继承了该类上的getMethodMatcher()方法,其最终会返回TransactionAttributeSourcePointcut对象本身。下面就不再梳理AopUtils#canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions)中的代码了,其主要目的就是遍历目标类中的所有方法,即TransferServiceImpl中的所有方法(包括继承过来的),然后调用MethodMatcher接口的matches()方法,这里调的是TransactionAttributeSourcePointcut类中的matches()方法。注意:这里只会判断当前类及其中是否有方法适合TransactionAttributeSourcePointcut指定的规则,如果适合就会直接返回true。这个方法的最终调用者,即AbstractAutoProxyCreator#wrapIfNecessary(Object bean, String beanName, Object cacheKey)方法,该方法会将得到的Advisor集合继续向下传递给createProxy()方法,以完成最终的代理的创建。(跟踪过程中隐约看到,会遍历所有方法为所有方法生成链?暂不确定后续会继续跟踪)

至此,配置了事务的类的代理对象就创建完成了。接下来我们主要看一下其执行过程中必须用到的Interceptor的实现类TransactionInterceptor。该类中的invoke(MethodInvocation)方法是关键,其源码如下所示:

public Object invoke(MethodInvocation invocation) throws Throwable {// Work out the target class: may be {@code null}.// The TransactionAttributeSource should be passed the target class// as well as the method, which may be from an interface.Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);// Adapt to TransactionAspectSupport's invokeWithinTransaction...return invokeWithinTransaction(invocation.getMethod(), targetClass, new CoroutinesInvocationCallback() {@Override@Nullablepublic Object proceedWithInvocation() throws Throwable {return invocation.proceed();}@Overridepublic Object getTarget() {return invocation.getThis();}@Overridepublic Object[] getArguments() {return invocation.getArguments();}});
}

该类会继续调用父类TransactionAspectSupport中的invokeWithinTransaction()方法。在该方法中可以看到这样几个对象:TransactionAttributeSource、TransactionAttribute、TransactionManager等。执行详情参见下图:

这里的TransactionAttributeSource的实际类型为AnnotationTransactionAttributeSrouce,接着会调用本类的determineTransactionManager()方法获取目标类上配置的与事务相关的属性,并将其包装为TransactionAttribute对象,实际类型为RuleBasedTransactionAttribute。接着获取其中配置的事务管理器(TransactionManager),其实际类型为JdbcTransactionManager。

送君千里终须一别,虽然还想继续,但终究还是想停一下!通过这篇文章我们再次回顾了AOP创建过程中寻找适当Advisor的流程,同时也了解了Spring事务创建过程中的一些重要类及其结构,最重要的是我们找到了Spring事务控制的核心代码。下篇文章我们将继续跟踪这个核心代码,以了解清楚Spring事务控制的核心原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/249406.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++初阶入门之命名空间和缺省参数的详细解析

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.前言 二.命名空间 2.1命名冲突的例子 2.2解决方案 2.3命…

MyBatis概述与MyBatis入门程序

MyBatis概述与MyBatis入门程序 一、MyBatis概述二、入门程序1.准备开发环境&#xff08;1&#xff09;准备数据库&#xff08;2&#xff09;创建一个maven项目 2.编写代码&#xff08;1&#xff09;打包方式和引入依赖&#xff08;2&#xff09;新建mybatis-config.xml配置⽂件…

node.js(nest.js控制器)学习笔记

nest.js控制器&#xff1a; 控制器负责处理传入请求并向客户端返回响应。 为了创建基本控制器&#xff0c;我们使用类和装饰器。装饰器将类与所需的元数据相关联&#xff0c;并使 Nest 能够创建路由映射&#xff08;将请求绑定到相应的控制器&#xff09;。 1.获取get请求传参…

祖传代码里的神逻辑

昨天做权限限制的需求&#xff0c;给自己配置了两个新的分组&#xff0c;然后就发现登录不了项目了&#xff0c;sql报错ORA-01795: maximum number of expressions in a list is 1000&#xff0c;一路debugger找到了元凶&#xff0c;看逻辑是想把两个不同表里的分组去重然后合并…

【CMU-自主导航与规划】M-TARE planner 配置与运行

M-TARE docker M-TARE 源码 一、依赖 Docker, Docker Compose, NVIDIA Container Toolkit, Nvidia GPU Driver&#xff08;需要至少2个&#xff0c;带Nvidia GPU&#xff09; 1.1 Docker docker -v #查询版本1.2 Docker Compose docker compose version1.3 …

分布式搜索引擎_学习笔记_3

分布式搜索引擎03 0.学习目标 1.数据聚合 **聚合&#xff08;aggregations&#xff09;**可以让我们极其方便的实现对数据的统计、分析、运算。例如&#xff1a; 什么品牌的手机最受欢迎&#xff1f;这些手机的平均价格、最高价格、最低价格&#xff1f;这些手机每月的销售…

Here Document免交互和Expect

文章目录 Here Document免交互和Expect自动化交互一、Here Document—免交互1、Here Document 免交互概述2、语法格式3、免交互的用法3.1 cat命令3.2 tee命令3.3 wc命令3.4 read命令3.5 passwd命令 4、Here Document 变量设定 二、Expect自动化交互1、expect基本使用1.1 脚本解…

【Linux】Linux基本指令

目录 1.ls指令 2.cd指令 3.touch指令 4.mkdir指令 5.rmdir指令和rm指令 5.1rmdir指令 5.2rm指令 6.man指令 7.cp指令 8.mv指令 9.cat指令 10.more指令 && less指令 10.1more指令 10.2less指令 11.head指令 && tail指令 11.1head指令 11.2tai…

【数学】【记忆化搜索 】【动态规划】964. 表示数字的最少运算符

作者推荐 【动态规划】【字符串】【表达式】2019. 解出数学表达式的学生分数 本文涉及知识点 动态规划汇总 数学 记忆化搜索 LeetCoce964表示数字的最少运算符 给定一个正整数 x&#xff0c;我们将会写出一个形如 x (op1) x (op2) x (op3) x … 的表达式&#xff0c;其中每…

自动保存知乎上点赞的内容至本地

背景&#xff1a;知乎上常有非常精彩的回答/文章&#xff0c;必须要点赞收藏&#xff0c;日后回想起该回答/文章时翻看自己的动态和收藏夹却怎么也找不到&#xff0c;即使之前保存了链接网络不好也打不开了&#xff08;。所以我一般碰到好的回答/文章都会想办法保存它的离线版本…

mac安装mysql的8.0设置面板启动不了

1、前言 记得之前安装mysql5.7的时候&#xff0c;是可以直接从设置里面的mysql面板启动的&#xff0c;但是到了mysql8.0之后就启动不了了&#xff0c;这个问题不知道是版本问题还是我换了m系列芯片的mysql导致的&#xff0c;之前很多次都启动不了&#xff0c;这次搞了下&#x…

2024年1月份实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先&#xff0c;来看下效果图 在线体验地址&#xff1a;https://geojson.hxkj.vip&#xff0c;并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

字觅网“正式上线登陆中国大陆,助力全球用户畅享正版字体服务

在中国上海,专注于提供正版字体授权服务的平台"字觅网"正式宣布在中国大陆上线,为全球用户提供更广泛、更便捷的正版字体选择。 "字觅网"以致力于推动正版字体服务为核心,通过深度合作,汇聚了众多国内知名字库,包括汉标字库、上首字库、汉呈字库、名家字库…

防火墙详解

一、基本定义 所谓“防火墙”是指一种将内部网和公众访问网&#xff08;如Internet&#xff09;分开的方法&#xff0c;它实际上是一种建立在现代通信网络技术和信息安全技术基础上的应用性安全技术&#xff0c;隔离技术。越来越多地应用于专用网络与公用网络的互联环境之中&a…

BGP同步规则

BGP同步规则&#xff1a;开启同步下&#xff0c;从IBGP收到一条路由不会传给任何EBGP邻居(实验效果IBGP邻居和EBGP邻居都不传)&#xff0c;除非从自身的IGP中也学到这条路由。目的是防止AS内部出现路由黑洞&#xff0c;向外部通告了一个本AS不可达的虚假的路由。 同步规则只影响…

win11设置mysql开机自启

目录 命令式 1、打开命令提示符或 PowerShell&#xff1a; 2、使用管理员权限运行命令行工具&#xff1a; 3、设置 MySQL 服务为开机自启动&#xff1a; 4、启动 MySQL 服务&#xff1a; 5、 验证设置是否生效&#xff1a; 操作视图式 1、右击任务栏 ---> 选择任务管…

安卓网格布局GridLayout

<?xml version"1.0" encoding"utf-8"?> <GridLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:layout_width"match_parent"android:la…

【C++】类和对象(1)

上节我们学习了C入门的一些语法知识&#xff0c;这篇博客来学习类和this指针。 目录 面向过程和面向对象的初步认识 类的引入 类的定义 类的访问限定符及封装 访问限定符 封装 类的作用域 类的实例化 类对象大小 this指针 this指针特性 面向过程和面向对象的初步认识…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之DatePicker组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之DatePicker组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、DatePicker组件 日期选择器组件&#xff0c;用于根据指定日期范围创建日期滑…

vue3开发,axios发送请求是携带params参数的避坑

vue3开发,axios发送请求是携带params参数的避坑&#xff01;今天一直报错&#xff0c;点击新增购物车&#xff0c;报错&#xff0c; 【Uncaught (in promise) TypeError: target must be an object】。查询了网上的资料说的都不对。都没有解决。最终还是被我整明白了。 网上网…