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 就进行回滚。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔道不误砍柴功

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值