Spring注解驱动之声明式事务源码分析

概述

环境搭建

导入相关依赖

<dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.44</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>4.3.12.RELEASE</version>
</dependency>

向IOC容器中注册一个c3p0数据源。

package com.meimeixia.tx;

import javax.sql.DataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.mchange.v2.c3p0.ComboPooledDataSource;

@EnableTransactionManagement // 它是来开启基于注解的事务管理功能的 
@ComponentScan("com.meimeixia.tx")
@Configuration
public class TxConfig {

	// 注册c3p0数据源
	@Bean
	public DataSource dataSource() throws Exception {
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		dataSource.setUser("root");
		dataSource.setPassword("liayun");
		dataSource.setDriverClass("com.mysql.jdbc.Driver");
		dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
		return dataSource;
	}
	
// 向IOC容器中注册一个JdbcTemplate组件,它是spring提供的一个简化数据库操作的工具。
```java
@Bean
public JdbcTemplate jdbcTemplate() throws Exception {
    JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource());
    return jdbcTemplate;
}

// 注册事务管理器在容器中
@Bean
public PlatformTransactionManager platformTransactionManager() throws Exception {
	return new DataSourceTransactionManager(dataSource());
}
}

注意,这个事务管理器有一个特别重要的地方,就是它要管理数据源,也就是说事务管理器一定要把数据源控制住。这样的话,它才会控制住数据源里面的每个连接,这时该连接上的回滚以及事务的开启等操作,都会有这个事务管理器来做。

写sql相关操作类

package com.meimeixia.tx;

import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class UserDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	
	public void insert() {
		String sql = "insert into `tbl_user`(username, age) values(?, ?)";
		String username = UUID.randomUUID().toString().substring(0, 5);
		jdbcTemplate.update(sql, username, 19); // 增删改都来调用这个方法
	}
}

package com.meimeixia.tx;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserService {

	@Autowired
	private UserDao userDao;
	
	@Transactional
	public void insertUser() {
	userDao.insert();
	// otherDao.other(); // 该方法中的业务逻辑势必不会像现在这么简单,肯定还会调用其他dao的方法
	System.out.println("插入完成...");
	
	int i = 10 / 0;
}
}

声明式事务原理

要想指定声明式事务的原理,只需要搞清楚@EnableTransactionManagement注解给容器中注册了什么组件,以及这些组织工作时候的功能是什么就行了。一旦把这个研究透了,声明式事务的原理就清楚了。
之前研究AOP的原理的时候,是从@EnableAspectJAutoProxy注解开始入手研究的,因此研究声明式事务的原理,我们也应该从@EnableTransactionManagement注解开始入手研究。
其实,当研究完声明式事务的原理,就会发现这一过程与研究AOP原理的过程是非常相似的,也可以说着俩原理几乎一摸一样。

@EnableTransactionManagement注解利用TransactionManagementConfigurationSelector给容器中导入组件

在配置类上添加@EnableTransactionManagement注解,便能够开启基于注解的事务管理功能。看看下面的源码。
在这里插入图片描述
从源码中可以看出,@EnableTransactionManagement注解使用@Import注解给容器中引入了TranactionManagementConfigurationSelector组件。其实TranactionManagementConfigurationSelector就是一个ImportSelector。
我们可以点到TransactionManagementConfigurationSelector类中一看究竟,如下图所示,发现它继承了一个类,叫AdviceModeImportSelector。
在这里插入图片描述
然后再次点到AdviceModeImportSelector类中,如下图所示,发现它实现了一个接口,叫ImportSelector。
在这里插入图片描述
说到底,其实它就是用于给容器中快速导入一些组件的,到底要导入哪些组件,就看它会返回哪些要导入到容器中的组件的全类名。
看源码就会发现里面会做一个switch判断,如果adviceMode是PROXY,就会返回一个String[],该String数组如下所示:

new String[] {AutoProxyRegistrar.class.getName(), ProxyTransactionManagementConfiguration.class.getName()};

这说明会向容器中导入AutoProxyRegistrar和ProxyTransactionManagementConfiguration这两个组件。

导入第一个组件(AutoProxyRegistrar)做了什么

在这里插入图片描述AutoProxyRegistrar实现了ImportBeanDefinitionRegistrar接口,说明AutoProxyRegistrar组件是来向容器中注册bean的,最终会调用该组件的registerBeanDefinitions()方法来向容器中注册bean。

AutoProxyRegistrar向容器中注入一个自动代理创建器InfrastructureAdvisorAutoProxyCreator

我们来仔细的看下AutoProxyRegistrar类中的registerBeanDefinitions()方法。
在这里插入图片描述
在该方法中先是通过如下一行代码来获取各种注解类型,这儿需要特别注意的是,这里是拿到所有的注解类型,而不是只拿@EnableAspectJAutoProxy这个类型的。因为mode、proxyTargetClass等属性会直接影响到代理的方式,而拥有这些属性的注解至少有@EnableTransactionManagement、@EnableAsync以及@EnableCaching等等,甚至还有启用AOP的注解,即@EnableAspectJAutoProxy,它也能设置proxyTargetClass这个属性的值,因此也会产生关联影响。

Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();

然后是拿到注解里的mode、proxyTargetClass这两个属性的值。
在这里插入图片描述
注意,如果这儿的注解是@Configuration或者别的其他注解的话,那么获取到的这俩属性的值就是null了。
接着做一个判断,如果存在mode、proxyTargetClass这两个属性,并且这两个属性的class类型也都是对的,那么便会进入到if判断语句中,这样,其余注解就相当于都被挡在外面了。
在这里插入图片描述
要是真进入到了if判断语句中,是不是意味着找到了候选的注解(例如@EnableTransactionManagement)呢?你仔细想一下,是不是这回事。找到了候选的注解之后,就将candidateFound标识置为true。

紧接着会再做一个判断,即判断找到的候选注解中的mode属性的值是否为AdviceMode.PROXY,若是则会调用我们熟悉的AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法。相信大家也很熟悉这个方法了,它主要是来向容器中注册一个InfrastructureAdvisorAutoProxyCreator组件的。
在这里插入图片描述
我们继续往下看AutoProxyRegistrar类的registerBeanDefinitions()方法。这时,又会做一个判断,要是找到的候选注解设置了proxyTargetClass这个属性的值,并且值为true,那么便会进入到下面的if判断语句中,看要不要强制使用CGLIB的方式。

如果此时找到的候选注解是@EnableTransactionManagement,想一想会发生什么事情?查看该注解的源码,你会发现它里面就拥有一个proxyTargetClass属性,并且其默认值是false。所以此时压根就不会进入到if判断语句中,而只会调用我们熟悉的AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法。
继续追踪源码我们就发现注册的是一个InfrastructureAdvisorAutoProxyCreator.class了。
在这里插入图片描述
现在我们得出一个结论:导入的第一个组件(AutoProxyRegistrar)向容器中注入了一个自动代理创建器,即InfrastructureAdvisorAutoProxyCreator。
我们继续追踪源码发现InfrastructureAdvisorAutoProxyCreator它其实是一个后置处理器。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

InfrastructureAdvisorAutoProxyCreator组件的功能

其实InfrastructureAdvisorAutoProxyCreator做的事情和之前研究的AOP的AnnotationAwareAspectJAutoProxyCreator组件所有的事情基本一样。就是利用后置处理器机制在对象创建以后进行包装,然后返回一个代理对象,并且该代理对象里面会有所有的事务增强器。最后代理对象执行目标方法,在执行的目标方法的过程过程中会利用拦截器的链式机制,依次进入每个拦截器中进行执行。

导入第二个组件(ProxyTransactionManagementConfiguration)的作用

向容器中注册事务增强器

源码中我们发现它是一个配置类,它会利用@Bean注解向容器中注册各种组件,而且注册的第一个组件就是BeanFactoryTransactionAttributeSourceAdvisor,这个Advisor是事务的核心内容,称之为事务增强器。
在这里插入图片描述

向容器中注册事务增强器时,要用到事务属性源

事务增强器是什么呢?从上面的配置类可以看出,在向容器中注册事务增强器时,它会需要一个TransactionAttributeSource,翻译过来应该是事务属性源。
你会发现所需的TransactionAttributeSource又是容器中的一个bean,它是一个AnnotationTransactionAttributeSource对象。这个是重点,它是基于注解驱动的事务管理的事务属性源,和@Transaction注解相关,也是现在使用最多的方式,其基本作用遇上比如@Transaction注解标准的方法是,此类会分析此事务注解。
继续追踪源码会发现有一个事务解析器接口。
在这里插入图片描述
相关属性就是我们在@Transaction注解里面的写的。
在这里插入图片描述

小结

事务增强器要用到事务注解的信息,它会使用AnnotationTransactionAttributeSource类来解析方法上标注的@Transtaction注解。

在向容器中注册事务增强器时,还需要用到事务拦截器

接下来,我们再来看看向容器中注册事务增强器时,还得做些什么。回到ProxyTransactionManagementConfiguration类中,发现在向容器中注册事务增强器时,除了需要事务注解信息,还需要一个事务的拦截器,看到那个transactionInterceptor方法没,它就是表示事务增强器还要用到一个事务的拦截器。
在这里插入图片描述
仔细查看上面的transactionInterceptor方法,你会看到在里面创建了一个TransactionInterceptor对象,创建完毕之后,不但会将事务属性源设置进去,而且还会将事务管理器(txManager)设置进去。也就是说,事务拦截器里面不仅保存了事务属性信息,还保存了事务管理器。

我们点进去TransactionInterceptor类里面去看一下,发现该类实现了一个MethodInterceptor接口,如下图所示。
在这里插入图片描述
看到它,会不会倍感亲切,因为在研究AOP原理的时候,就认识它了。AOP里面的一个知识点:切面类里面的通知方法最终都会被整成增强器,而增强器又会被转成MethodInterceptor。所以,这个事务拦截器的本质上还是一个MethodInterceptor(方法拦截器)。
所谓方法拦截器就是会在容器中放一个代理对象,代理对象要执行目标方法,那么方法拦截器就会进行工作。
其实,跟我妈之前研究AOP的原理一摸一样,在代理对象执行目标方法的时候,它便会来执行拦截器链,而现在这个拦截器链,只有一个TransactionInterceptor,它正是这个拦截器。
仔细翻阅TransactionInterceptor类的源码,你会发现它里面有一个invoke方法,而且还会看到在该方法里面又调用了一个invokeWithinTransaction方法,如下图所示。
在这里插入图片描述
在这里插入图片描述

先来获取事务相关的属性信息

从invokeWithinTransaction方法的第一行代码,即:

final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
再来获取PlatformTransactionManager

接着往下看invokeWithinTransaction方法,可以看到它的第二行代码是这样写的:

final PlatformTransactionManager tm = determineTransactionManager(txAttr);

这就是来获取PlatformTransactionManager的,还记得我们之前就已经向容器中注册了一个吗,现在就是来获取它的。那到底又是怎么来获取的呢?我们不妨点进去determineTransactionManager方法里面去看一下。
在这里插入图片描述
首次获取肯定就为null,但没关系,因为最终会从容器中按照类型来获取,这可以从下面这行代码中看出来。

defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class);

所以,我们只需要给容器中注入一个PlatformTransactionManager,正如我们前面写的这样:

// 注册事务管理器在容器中
@Bean
public PlatformTransactionManager platformTransactionManager() throws Exception {
    return new DataSourceTransactionManager(dataSource());
}
总结

如果事先没有添加任何TransactionManager,那么最终会从容器中按照类型来获取一个PlatformTransactionManager。

执行目标方法

接下来,继续往下看invokeWithinTransaction方法,来看它接下去又做了些什么。其实,很容易就能看出来,获取到事务管理器之后,然后便要来执行目标方法了,而且如果目标方法执行时一切正常,那么还能拿到一个返回值,如下图所示。
在这里插入图片描述

TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);

面这个方法翻译成中文,就是如果是必须的话,那么得先创建一个Transaction。说人话,就是如果目标方法是一个事务,那么便开启事务。

如果目标方法执行时一切正常,那么接下来该怎么办呢?这时,会调用一个叫commitTransactionAfterReturning的方法,如下图所示。
在这里插入图片描述
我们可以点进去commitTransactionAfterReturning方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器提交事务,如下图所示。
在这里插入图片描述
如果执行目标方法时出现异常,那么又该怎么办呢?这时,会调用一个叫completeTransactionAfterThrowing的方法,如下图所示。
在这里插入图片描述
我们可以点进去completeTransactionAfterThrowing方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器回滚这次操作,如下图所示。
在这里插入图片描述
也就是说,真正的回滚与提交事务的操作都是由事务管理器来做的,而TransactionInterceptor只是用来拦截目标方法的。

以上就是我们通过简单地来分析源码,粗略地了解了一下整个事务控制的原理。

总结

首先,使用AutoProxyRegistrar向Spring容器里面注册了一个后置处理器,这个后置处理器会负责给我们包装代理对象。然后使用ProxyTransactionManagementConfiguration再向容器中注册一个事务增强器,此时需要用的事务拦截器。最后代理对象执行目标方法,在执行目标方法的过程中便会执行代理类里面的拦截器链,而且每次在执行目标方法异常时,便会利用事务管理器进行事务回滚,如果执行一切正常,那么就好使用事务管理器提交事务。

参考

Spring注解驱动开发第34讲——你了解基于注解版的声明式事务吗?

Spring注解驱动开发第35讲——声明式事务原理的源码分析

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

融极

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

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

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

打赏作者

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

抵扣说明:

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

余额充值