Spring 事务处理流程源码浅析

注解式事务

大家都知道想要 Spring 的事务功能,你就必须使用 @EnableAspectJAutoProxy 注解开启事务,并且需要再方法或者类或接口上标注 @Transactional 注解。

使用案例

@Configuration
@EnableTransactionManagement
@ComponentScan("com.cn.spring.service")
public class TxMainConfig {

	@Bean
	public DataSource dataSource() {
		System.out.println("开始拦截数据源");
		DriverManagerDataSource dataSource = new DriverManagerDataSource();
		dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://localhost:3306/db?useSSL=false&allowPublicKeyRetrieval=true");
		dataSource.setUsername("root");
		dataSource.setPassword("8888");
		System.out.println("数据源链接成功!");
		return dataSource;
	}

	@Bean
	public JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}
	
	// 事物管理器
	@Bean
	public JdbcTransactionManager jdbcTransactionManager() {
		return new JdbcTransactionManager(dataSource());
	}
	
	@Bean
	public TransactionTemplate transactionTemplate() {
		TransactionTemplate template = new TransactionTemplate();

		template.setTransactionManager(jdbcTransactionManager());
		return template;
	}

}

定义业务类如下:

@Service
public class CalculateServiceImpl implements CalculateService {

	@Autowired
	private OrderService orderService;

	@Autowired
	private StockService stockService;

	@Override
	@Transactional // 默认传播机制
	public void add(int a, int b) {
		try {
			orderService.crateOrder();

			stockService.addStock();

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

那么思考下,为什么在 Spring 中标注了这两个注解(@EnableTransactionManagement、@Transactional)就能够
帮我们完成事务功能?

那么现在就看下这两个注解到底干了什么什么事情。先看 @EnableTransactionManagement 源码如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

从源码可以清楚的看到通过 @Import 注解导入了两个核心类:InfrastructureAdvisorAutoProxyCreatorProxyTransactionManagementConfiguration

其实猜都能猜到,第一个类肯定使用来生成代理类的、第二个肯定是导入一些事务相关切面逻辑。

进入到 InfrastructureAdvisorAutoProxyCreator 类可以发现此类是 AOP 切面功能 AbstractAutoProxyCreator 抽象类的子类,而且 AbstractAutoProxyCreator 是一个 BeanPostProcessor 后置处理器,在 Spring 启动过程中会被回调。

BeanPostProcessor 和 SmartInstantiationAwareBeanPostProcessor 接口

实现了上面接口的类,只需要关注接口中定义的规范 API 即可,在 Spring 启动过程中就会去回调这些接口的方法,源码如下:

在这里插入图片描述

在这里插入图片描述

然后去查看 InfrastructureAdvisorAutoProxyCreator 类看能否找到以上规范的 API 方法,发现找不到,但是它继承了 AbstractAdvisorAutoProxyCreator 类,所以就去它的父类中查找,发现也没有,那就在往上一个父类找,也就是 AbstractAutoProxyCreator (AOP 切面功能抽象类,AOP 中王者级别的类),发现终于找到了后置处理器中定义的规范 API 方法,源码如下:

在这里插入图片描述

首先看到 getEarlyBeanReference() 方法,源码如下:

在这里插入图片描述

发现第一行是把当前过来的原生包成品 bean 保存到了一个缓存中,为什么要保存呢?因为怕重复创建代理对象,第二行就是创建代理对象的逻辑,这个 wrapIfNecessary() 方法会在两个地方被调用,第一个就是这里的 getEarlyBeanReference() 方法,还有一个地方就是后置处理器的另一个 API 方法中——postProcessAfterInitialization() 方法中。

getEarlyBeanReference() 和 postProcessAfterInitialization() 都是后置处理器的规范 API,所以必然存在先后被回调到,所以这里不管你是哪个先被调用,你肯定需要一个缓存或者是标记说你干了什么事?这里就是使用一个 Map 类型 earlyProxyReferences 缓存表示在 getEarlyBeanReference() API 中创建过了代理对象,其他 API 中不需要再去创建了,第二个 API 调用创建代理对象的源码如下:

在这里插入图片描述

其他 API 不需要去关注,因为没有起到关键性作用,所以这里重点关注 getEarlyBeanReference() 和 postProcessAfterInitialization(),但是会发现这两个是做的同一件事情,所以只需要关注 getEarlyBeanReference() 这个 API 的逻辑即可。

进入到 wrapIfNecessary() 方法,源码如下:

在这里插入图片描述

这里有两个非常重要的步骤:

  • 判断是否需要生成代理对象
  • 创建代理对象

判断是否需要生成代理对象

进入到 getAdvicesAndAdvisorsForBean() 源码如下:

在这里插入图片描述

在这里插入图片描述

先看第一行代码,去收集实现了 Advisor 的接口子类,事务通过注解 @EnableTransactionManagement 导入了一个 Advisor,然后这里就会找到一个 Advisor 候选者,源码如下:

在这里插入图片描述

接着看第二行代码,就是找到的 Advisor 是否能够用于此类,能够能够用于此类,那就是表示这个类需要被增强,就需要被代理了,就需要创建这个类的代理类了。进入 findAdvisorsThatCanApply() 方法,会从方法和类进行匹配,源码如下:

在这里插入图片描述

在这里插入图片描述

获取都 ClassFilter 对象匹配类,答案是这里匹配不到,因为我们的 @Transactional 注解是标在了方法上的,所以又会往类的所有方法上去匹配,每个方法挨个去比较看是否能够找到 @Transactional 注解。

在这里插入图片描述

接下来我们进入事务注解的匹配过程,TransactionAttributeSourcePointcut 负责去匹配,看下到底是怎么匹配的,源码如下:

南海中一定要一个意识: 切面的匹配功过程一定会有 Pointcut 切入点,并且每个 Pointcut 一定会有两个元素:就是 ClassFilter 对类的匹配执行器,MethodMatcher 对方法的匹配执行器

在这里插入图片描述

进入到 getTransactionAttributeSource() 方法,源码如下:

在这里插入图片描述

为什么敢断定这个 transactionAttributeSource 变量不是 null ? 如下源码:在 TransactionAttributeSourceAdvisor 类的构造中赋值了这个 transactionAttributeSource 变量

在这里插入图片描述

这个 ProxyTransactionManagementConfiguration 是注解 @EnableTransactionManagement 导入进来的类。
然后 getTransactionAttributeSource() 就获取到了一个事务属性对象,transactionAttributeSource 这个对象主要用来封装 @Transactional 注解中解析出来的值(传播特性,隔离级别、只读等等)。

继续往下看,看是怎么匹配这个注解的

在这里插入图片描述

在这里插入图片描述

重点都是在 computeTransactionAttribute() 方法,这里面回去判断方法、类、接口是否被 @Transactional 修饰,进入源码:

在这里插入图片描述

进入 findTransactionAttribute() 方法,源码如下:

在这里插入图片描述

在这里插入图片描述


在这里插入图片描述

从上述源码中可以看出就是去查找这个类,方法、接口、接口方法中是否存在 @Transactional 注解,如果有就要开始往下代理对象了。

至此第一个步骤:匹配过程就算是结束了,接下来开始要去创建代理对象

创建代理对象

回到外层调用处,源码如下:

在这里插入图片描述


在这里插入图片描述

可以看到我们的目标对象被包装成了一个 SingletonTargetSource 对象,进入到 createProxy() 源码如下:

在这里插入图片描述

首先过来就创建了一个 ProxyFactory 的代理工厂,拷贝了一下当前对象的全部属性到工厂中,把目标对象放到了 ProxyFactory、Advisors 增强器放到了 ProxyFactory。这个对象包装了代理需要的所有东西,就是工厂,里面存放着代理相关的东西

还会推算你的目标 bean 是否有接口,如果没有就会使用 Cglib、有就使用 Proxy 代理,我们这里是有接口的,所以使用 Proxy 代理。

进入方法 getProxy() 内部,源码如下:

在这里插入图片描述

在这里插入图片描述

然后再进入 getProxy() 方法,源码如下:

在这里插入图片描述

这里就是 JDK 动态代理代码了,this 是上面创建的对象 JdkDynamicAopProxy,此类实现了 InvocationHandler 接口,源码如下:

在这里插入图片描述

至此创建代理对象也算完成了,代理对象的 InvocationHandler 是 JdkDynamicAopProxy,也就是说当你调用方法的时候,就会调用到 JdkDynamicAopProxy 类中的 invoke() 方法,这是动态代理最基本调用。

调用过程

测试代码如下:

在这里插入图片描述

因为这里的 CalculateService 类需要被代理,所以这里会生成代理对象,calculateService 对象是个$Proxy23@222 对象,当调用 add() 方法时,会调用到 JdkDynamicAopProxy 中的 invoke() 方法,因为 JdkDynamicAopProxy 实现了 InvocationHandler 接口,源码如下:

在这里插入图片描述

第一行其实去判断这个方法是否需要被增强,其实你走到这里,类的匹配根本就不需要做了,因为走到这里必然是代理类,现在只需要去匹配这个方法需不需被增强,进入 getInterceptorsAndDynamicInterceptionAdvice()方法,源码如下:

在这里插入图片描述

无非就是一个 matches() 匹配过程,如果匹配返回 true,表示这个方法需要被增强,就会进入 registry.getInterceptors(advisor) 方法,去获取对这个方法增强逻辑 Advice,源码如下:

在这里插入图片描述


在这里插入图片描述

发现是返回的一个 TransactionInterceptor transactionInterceptor 对象,其实就是一个 Advice,TransactionInterceptor 就是 Advice 的子类而已,而且这个 TransactionInterceptor 拦截器是通过 @EnableTransactionManagement 注解导入进来的,源码如下:

在这里插入图片描述

在这里插入图片描述

所以这个事物的所有增强逻辑都是在这里做的,这些增强逻辑其实就是帮我们自动开启事务,自动回滚,自动提交。所以这就是为什么我们直接添加两个注解能够帮我们完成事物控制。其实都是这块的 Advice 拦截器完成的。

至此 getInterceptorsAndDynamicInterceptionAdvice() 方法就算完成了,而且返回了一个 TransactionInterceptor Advice,再回到源码开始执行 proceed() 方法,源码如下:

在这里插入图片描述

此时 chain 不为空,就会执行 else 逻辑,进入到 ReflectiveMethodInvocation 类,源码如下:

在这里插入图片描述

执行 invoke() 方法,调用 TransactionInterceptor 事务提供的 Advice 增强功能,源码如下:

在这里插入图片描述

进入到 invokeWithinTransaction() 方法,源码如下:

在这里插入图片描述

首先过来就获取 @Transactional 解析封装好的事务对象 TransactionAttributeSource,然后获取事务管理器,继续往下看,源码如下:

在这里插入图片描述

看到这三个步骤是不是就恍然大悟了,原来事务相关的功能都是在这里帮我们做好了。

进入开启事务的方法 createTransactionIfNecessary() 方法,源码如下:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

然后调用我们自己的业务方法,源码如下:

在这里插入图片描述

然后再看事务回滚的方法 completeTransactionAfterThrowing(),源码如下:

在这里插入图片描述

然后再看下事务提交的方法 commitTransactionAfterReturning() 源码如下:

在这里插入图片描述

至此声明式事务的执行流程分析已经算完成了,还有事物的传播特性没有分析,是因为这一块内容也挺多的,篇幅太大不太好,所以放在了另一篇文章中,下面看下编程式事务流程分析。

编程式事务

为什么还要这个编程式事务?因为很多时候注解不够灵活,如果自己来管控事务是不是比较好,而且一个注解开启的事物,如果业务逻辑中,有一个接口耗时非常的久,那么就会阻塞住 Connection 连接,连接没有释放,如果请求很多,那就完蛋了,资源可能就不够用了,但是编程式事务就比较好的可以自己控制。

代码如下:


	// 编程式事务
	@Override
	public void reduce(int a, int b) {
		System.out.println("start reduce......");
		// 第一种方式: 什么都不用自己控制
		template.execute(status -> {
		    // 这里面还可以继续嵌套事务
			//orderService.crateOrder();
			// ...
			// 要是里面还有要执行的耗时逻辑又可以重新套一个execute 操作
			// ...
			return null;
		});

		template.execute(status -> {
		    // 这里面还可以继续嵌套事务
			//stockService.addStock();
			// ...
			// 要是里面还有要执行的耗时逻辑又可以重新套一个execute 操作
			// ...
			return null;
		});

		// 第二种方式: 自己控制 commit 和 rollback
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
		TransactionStatus transaction = jdbcTransactionManager.getTransaction(definition);

		try {
			System.out.println("一些耗时的业务逻辑代码.....");
			stockService.addStock();
			orderService.crateOrder();
		} catch (TransactionException e) {
			// 自己控制回滚操作
			jdbcTransactionManager.rollback(transaction);
		}
		// 自己控制提交操作
		jdbcTransactionManager.commit(transaction);
	}

源码很简单,就对 execute() 这个分析,源码如下:

在这里插入图片描述

先去获取数据源对象,然后开启事务并设置手动提交,然后调用目标方法,执行完之后,调用 commit() 提交,如果出现异常或者 Error 就进行回滚。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340