Spring注解驱动开发第35讲——声明式事务原理的源码分析
发布日期:2021-06-30 17:56:22 浏览次数:5 分类:技术文章

本文共 11867 字,大约阅读时间需要 39 分钟。

文章目录

写在前面

上一讲,我向大家简单介绍了一下注解版的声明式事务,相信大家已经会初步使用了。这一讲,咱们就从源码的角度分析一下声明式事务的原理,让大家对声明式事务的原理有一个更加深入的认识。

声明式事务的原理

其实,要想知道声明式事务的原理,只需要搞清楚@EnableTransactionManagement注解给容器中注册了什么组件,以及这些组件工作时候的功能是什么就行了,一旦把这个研究透了,那么声明式事务的原理我们就清楚了。

你有没有发现,咱们之前研究AOP的原理时,是从@EnableAspectJAutoProxy注解开始入手研究的,这时你就应该能想到,研究声明式事务的原理,也是应该从@EnableTransactionManagement注解开始入手研究。

其实,当你研究完声明式事务的原理,你就会发现这一过程与研究AOP原理的过程是非常相似的,也可以说这俩原理几乎是一模一样的。

接下来,我们就要从@EnableTransactionManagement注解开始一步一步分析声明式事务的原理了。只不过这次不会像之前分析AOP原理一样,以debug的模式来分析,而是直接点进源码里面看源码来分析。

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

在配置类上添加@EnableTransactionManagement注解,便能够开启基于注解的事务管理功能。那下面我们就来看一看它的源码,如下图所示。

在这里插入图片描述

从源码中可以看出,@EnableTransactionManagement注解使用@Import注解给容器中引入了TransactionManagementConfigurationSelector组件。那这个TransactionManagementConfigurationSelector又是啥呢?它其实是一个ImportSelector。

这是怎么得出来的呢?我们可以点到TransactionManagementConfigurationSelector类中一看究竟,如下图所示,发现它继承了一个类,叫AdviceModeImportSelector。

在这里插入图片描述

然后再次点到AdviceModeImportSelector类中,如下图所示,发现它实现了一个接口,叫ImportSelector。

在这里插入图片描述

这时,你总该相信TransactionManagementConfigurationSelector是一个ImportSelector了吧!其实,我们已经对ImportSelector接口有了一定程度的认识了,如果你还不知道它,那么不妨看看我写的这篇博客。

说到底,其实它是用于给容器中快速导入一些组件的,到底要导入哪些组件,就看它会返回哪些要导入到容器中的组件的全类名。

我们可以看一下TransactionManagementConfigurationSelector类的源码,看看它里面到底是怎么写的。其实在上面我们就看清楚该类的源码了,在它里面会做一个switch判断,如果adviceMode是PROXY,那么就会返回一个String[],该String数组如下所示:

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

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

如果adviceMode是ASPECTJ,那么便会返回如下这样一个String[]

new String[] {
TransactionManagementConfigUtils.TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME};

TRANSACTION_ASPECT_CONFIGURATION_CLASS_NAME一下,可以看到,它其实就是AspectJTransactionManagementConfiguration类的全类名,如下图所示。

在这里插入图片描述

也就是说,如果adviceMode是ASPECTJ,那么就会向容器中导入一个AspectJTransactionManagementConfiguration组件。只可惜,它和我们研究声明式事务的原理没有半毛钱的关系。

那么问题来了,AdviceMode又是个啥呢?点它,发现它是一个枚举,如下图所示。

在这里插入图片描述

这个枚举有啥子用呢?我们可以再来看一下@EnableTransactionManagement注解的源码,发现它里面会定义一个mode属性,且其默认值就是AdviceMode.PROXY。既然如此,那么便会进入到TransactionManagementConfigurationSelector类的switch语句的case PROXY选项中,这时,就会向容器中快速导入两个组件,一个叫AutoProxyRegistrar,一个叫ProxyTransactionManagementConfiguration。

接下来,我们便要来分析这两个组件的功能了,只要分析清楚了,声明式事务的原理就呼之欲出了。

导入的第一个组件(即AutoProxyRegistrar),它到底做了些啥呢?

我们来看导入的第一个组件,即AutoProxyRegistrar,它都做了些啥?我们点进去该类里面看一看,发现它实现了一个接口,叫ImportBeanDefinitionRegistrar。

在这里插入图片描述

关于该接口的详细介绍,你可以参考我写的这篇博客。说到底,这个AutoProxyRegistrar组件其实就是用来向容器中注册bean的,那你就应该清楚,最终会调用该组件的registerBeanDefinitions()方法来向容器中注册bean。

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

那么会向容器中注册什么bean呢?我们仔细地看一下AutoProxyRegistrar类中的registerBeanDefinitions()方法,如下图所示。

在这里插入图片描述

这里,我就粗略地分析一下该方法,真的就只是粗略地分析一下,如有错误,可以告知笔者改正。

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

Set
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方法。

这个咱们再熟悉不过的registerAutoProxyCreatorIfNecessary方法会向容器中注册什么呢?上面我也说到了,它会向容器中注册一个InfrastructureAdvisorAutoProxyCreator组件,即自动代理创建器。我是咋知道的呢?只能是看源码呗,还能是什么。点进去registerAutoProxyCreatorIfNecessary方法中,如下图所示,可以看到这个方法又调用了一个同名的重载方法。

在这里插入图片描述

然后点进去同名的重载方法中,如下图所示,可以看到这个方法又调用了一个registerOrEscalateApcAsRequired方法,而且还传入了一个参数,即InfrastructureAdvisorAutoProxyCreator.class

在这里插入图片描述

你现在该知道调用AopConfigUtils工具类的registerAutoProxyCreatorIfNecessary方法会向容器中注册什么组件了吧!

现在我们可以得出这样一个结论:导入的第一个组件(即AutoProxyRegistrar)向容器中注入了一个自动代理创建器,即InfrastructureAdvisorAutoProxyCreator。

其实,大家可以好好看一下AopConfigUtils工具类的源码,因为它里面还有一个我们非常熟悉的东东,这个东东是什么呢?我就不卖关子了,直接查看AopConfigUtils工具类第90行源码,你就能看到异常熟悉的东东了,它就是AnnotationAwareAspectJAutoProxyCreator,如下图所示。

在这里插入图片描述

大家还记得它是什么吗?这个时候你就需要回顾一下以前学习的内容了。当初咱们在研究AOP的原理时,不是得出了这样一个结论吗?即@EnableAspectJAutoProxy注解会利用AspectJAutoProxyRegistrar向容器中注入一个AnnotationAwareAspectJAutoProxyCreator组件。 现在,你总算该记起来了吧😊

声明式事务的原理跟AOP的原理很相似,只不过对于声明式事务原理而言,它注入的是InfrastructureAdvisorAutoProxyCreator组件而已。我们都知道,在研究AOP原理时,AnnotationAwareAspectJAutoProxyCreator实质上是一个后置处理器,那么InfrastructureAdvisorAutoProxyCreator实质上又是一个什么呢?也会是一个后置处理器吗?

点进去InfrastructureAdvisorAutoProxyCreator类里面去看一看,如下图所示,发现它继承了一个AbstractAdvisorAutoProxyCreator类。

在这里插入图片描述

然后再点进去AbstractAdvisorAutoProxyCreator类里面去看一看,如下图所示,发现它继承了一个AbstractAutoProxyCreator类。

在这里插入图片描述

接着再点进去AbstractAutoProxyCreator类里面去看一看,如下图所示,发现它实现了一个SmartInstantiationAwareBeanPostProcessor接口。

在这里插入图片描述

这说明注入的InfrastructureAdvisorAutoProxyCreator组件同样也是一个后置处理器。接下来我们就来分析一下该组件的功能。

InfrastructureAdvisorAutoProxyCreator组件的功能

在这一小节中,我们来粗略地分析一下注入的InfrastructureAdvisorAutoProxyCreator组件到底都做了些什么。

其实,它做的事情也很简单,和之前研究AOP原理时向容器中注入的AnnotationAwareAspectJAutoProxyCreator组件所做的事情基本上没差别,只是利用后置处理器机制在对象创建以后进行包装,然后返回一个代理对象,并且该代理对象里面会存有所有的增强器。最后,代理对象执行目标方法,在此过程中会利用拦截器的链式机制,依次进入每一个拦截器中进行执行。

这儿,我也只是寥寥几笔概括了一下,并没有仔细地分析,主要是之前我在研究AOP原理的时候,详细分析过了,而且是一步一步地认真分析,这消耗了我大量的精力与时间,令我倍感疲惫。

导入的第二个组件(即ProxyTransactionManagementConfiguration),它又到底做了些啥呢?

接下来,我们再来看导入的第二个组件,即ProxyTransactionManagementConfiguration,它又做了些啥?

向容器中注册事务增强器

点进去ProxyTransactionManagementConfiguration类里面去看一看,很快你就会发现它是一个配置类,它会利用@Bean注解向容器中注册各种组件,而且注册的第一个组件就是BeanFactoryTransactionAttributeSourceAdvisor,这个Advisor可是事务的核心内容,可以暂时称之为事务增强器。

在这里插入图片描述

总之一句话,以上配置类会利用@Bean注解向容器中注册一个事务增强器。

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

那么这个所谓的事务增强器又是什么呢?从上面的配置类中可以看出,在向容器中注册事务增强器时,它会需要一个TransactionAttributeSource,翻译过来应该是事务属性源。

很快,你就会发现所需的TransactionAttributeSource又是容器中的一个bean,而且从transactionAttributeSource方法中可以看出,它是new出来了一个AnnotationTransactionAttributeSource对象。这个是重点,它是基于注解驱动的事务管理的事务属性源,和@Transactional注解相关,也是现在使用得最多的方式,其基本作用是遇上比如@Transactional注解标注的方法时,此类会分析此事务注解。

然后,点进AnnotationTransactionAttributeSource类的无参构造方法中去看一看,发现该方法又调用了如下一个this(true)方法,即本类的另一个重载的有参构造方法。

在这里插入图片描述

接着,点击一下this(true)方法,这时会跳到如下的一个有参构造方法处。

在这里插入图片描述

在该方法中,你会看到一个TransactionAnnotationParser接口,源码如下图所示。

在这里插入图片描述

顾名思义,它是解析方法/类上事务注解的,当然了,你也可以称它为事务注解的解析器。

这里我要说明的一点是,Spring支持三个不同的事务注解,它们分别是:

  1. Spring事务注解,即org.springframework.transaction.annotation.Transactional(纯正血统,官方推荐)
  2. JTA事务注解,即javax.transaction.Transactional
  3. EJB 3事务注解,即javax.ejb.TransactionAttribute

因为现在基本上都是Spring的天下了,所以我们一般都会使用Spring事务注解。另外,上面三个注解虽然语义上一样,但是使用方式上不完全一样,若真要使用其它的则请注意各自的使用方式。

上面说到了Spring支持三个不同的事务注解,这里很显然,它们都对应了三个不同的注解解析器,即SpringTransactionAnnotationParser、JtaTransactionAnnotationParser以及Ejb3TransactionAnnotationParser。

也是因为现在基本上都是Spring的天下了,所以本文只会讲述SpringTransactionAnnotationParser,其它的雷同。我们可以点进去该类里面看一看,尤其要注意翻阅parseTransactionAnnotation方法,你会发现它就是来解析@Transactional注解里面的每一个信息的,包括它里面的每一个属性,例如rollbackFor、noRollbackFor、···

在这里插入图片描述

rollbackFor、noRollbackFor等等这些属性就是我们可以在@Transactional注解里面能写的。

在这里插入图片描述

小结

事务增强器要用到事务注解的信息,它总该得知道哪个方法是事务吧😊,所以这儿会使用到一个叫AnnotationTransactionAttributeSource的类,用它来解析事务注解。

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

接下来,我们再来看看向容器中注册事务增强器时,还得做些什么。回到ProxyTransactionManagementConfiguration类中,发现在向容器中注册事务增强器时,除了需要事务注解信息,还需要一个事务的拦截器,看到那个transactionInterceptor方法没,它就是表示事务增强器还要用到一个事务的拦截器。

在这里插入图片描述

仔细查看上面的transactionInterceptor方法,你会看到在里面创建了一个TransactionInterceptor对象,创建完毕之后,不但会将事务属性源设置进去,而且还会将事务管理器(txManager)设置进去。也就是说,事务拦截器里面不仅保存了事务属性信息,还保存了事务管理器。

我们点进去TransactionInterceptor类里面去看一下,发现该类实现了一个MethodInterceptor接口,如下图所示。

在这里插入图片描述

看到它,你是不是倍感亲切,因为咱们在研究AOP的原理时,就已经认识它了。相信你应该还记得这样一个知识点,切面类里面的通知方法最终都会被整成增强器,而增强器又会被转换成MethodInterceptor。所以,这样看来,这个事务拦截器实质上还是一个MethodInterceptor(方法拦截器)。

啥叫方法拦截器呢?简单来说就是,现在会向容器中放一个代理对象,代理对象要执行目标方法,那么方法拦截器就会进行工作。

其实,跟我们以前研究AOP的原理一模一样,在代理对象执行目标方法的时候,它便会来执行拦截器链,而现在这个拦截器链,只有一个TransactionInterceptor,它正是这个事务拦截器。接下来,我们就来看看这个事务拦截器是怎样工作的,即它的作用是什么。

仔细翻阅TransactionInterceptor类的源码,你会发现它里面有一个invoke方法,而且还会看到在该方法里面又调用了一个invokeWithinTransaction方法,如下图所示。

在这里插入图片描述

点进去invokeWithinTransaction方法里面看一下,你就能知道这个事务拦截器是怎样工作的了。

哎呀🙃!你不仅感叹一声,这个方法未免也写得太长了吧!确实是太长了,不过为了大家能看得更加清楚,我还是把整个方法给截出来给大家看看。

在这里插入图片描述

看看,是不是足够长啊😱!下面我就来详细讲述一下该方法。

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

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

final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);

我们便可以知道,这儿是来获取事务相关的一些属性信息的。

再来获取PlatformTransactionManager

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

final PlatformTransactionManager tm = determineTransactionManager(txAttr);

这就是来获取PlatformTransactionManager的,还记得我们之前就已经向容器中注册了一个吗,现在就是来获取它的。那到底又是怎么来获取的呢?我们不妨点进去determineTransactionManager方法里面去看一下。

在这里插入图片描述

这个方法写的还是蛮长的,不过没关系啊,下面我会为大家详细说说该方法。

先来看看下面这几行代码,即:

// ···String qualifier = txAttr.getQualifier();if (StringUtils.hasText(qualifier)) {
return determineQualifiedTransactionManager(qualifier);}// ···

这几行代码说的是啥意思呢?它是说,如果事务属性里面有Qualifier这个注解,并且这个注解还有值,那么就会直接从容器中按照这个指定的值来获取PlatformTransactionManager。

我这样一讲,相信你更加摸不着头脑了,这说的是啥啊😱!且听我娓娓道来,其实我们在为某个业务方法标注@Transactional注解的时候,是可以明确地指定事务管理器的名字的,不信你看:

在这里插入图片描述

从上图中可以看到,指定事务管理器的名字,其实就等同于Qualifier这个注解。虽说是可以明确指定事务管理器的名字,但我们一般都不这么做,即不指定。

如果真要是指定了的话,那么就应该是到这儿来判断了。

else if (StringUtils.hasText(this.transactionManagerBeanName)) {
return determineQualifiedTransactionManager(this.transactionManagerBeanName);}

上面这几行代码应该是来判断PlatformTransactionManager是否有名,若有则就应该像上面这么来获取。希望我理解的没有错😊

如果没指定的话,那么就是来获取默认的了,这时很显然会进入到最下面的else判断中。

else {
PlatformTransactionManager defaultTransactionManager = getTransactionManager(); if (defaultTransactionManager == null) {
defaultTransactionManager = this.transactionManagerCache.get(DEFAULT_TRANSACTION_MANAGER_KEY); if (defaultTransactionManager == null) {
defaultTransactionManager = this.beanFactory.getBean(PlatformTransactionManager.class); this.transactionManagerCache.putIfAbsent( DEFAULT_TRANSACTION_MANAGER_KEY, defaultTransactionManager); } } return defaultTransactionManager;}

可以看到,会先调用getTransactionManager方法,获取的是默认向容器中自动装配进去的PlatformTransactionManager。

首次获取肯定就为null,但没关系,因为最终会从容器中按照类型来获取,这可以从下面这行代码中看出来。

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

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

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

然后就能获取到PlatformTransactionManager了。获取到了之后,当然就可以使用它了。

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

执行目标方法

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

在这里插入图片描述

不知你有没有看到,在执行上面这句代码之前,还有这样一句代码:

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

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

如果目标方法执行时一切正常,那么接下来该怎么办呢?这时,会调用一个叫commitTransactionAfterReturning的方法,如下图所示。

在这里插入图片描述

我们可以点进去commitTransactionAfterReturning方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器提交事务,如下图所示。

在这里插入图片描述

如果执行目标方法时出现异常,那么又该怎么办呢?这时,会调用一个叫completeTransactionAfterThrowing的方法,如下图所示。

在这里插入图片描述

我们可以点进去completeTransactionAfterThrowing方法里面去看一看,发现它是先获取到事务管理器,然后再利用事务管理器回滚这次操作,如下图所示。

在这里插入图片描述

也就是说,真正的回滚与提交事务的操作都是由事务管理器来做的,而TransactionInterceptor只是用来拦截目标方法的。

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

总结

最后,我来总结一下声明式事务的原理。

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

转载地址:https://liayun.blog.csdn.net/article/details/111872692 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:Spring注解驱动开发第36讲——或许,这是你以前没看过的从源码角度理解BeanFactoryPostProcessor的原理
下一篇:Spring注解驱动开发第34讲——你了解基于注解版的声明式事务吗?

发表评论

最新留言

很好
[***.229.124.182]2024年04月17日 07时10分48秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

神经网络调参实战(四)—— 加深网络层次 & 批归一化 batch normalization 2019-04-30
数据挖掘与数据分析(三)—— 探索性数据分析EDA(多因子与复合分析) & 可视化(1)—— 假设检验(μ&卡方检验&方差检验(F检验))&相关系数(皮尔逊&斯皮尔曼) 2019-04-30
RRT算法(快速拓展随机树)的Python实现 2019-04-30
路径规划(二) —— 轨迹优化(样条法) & 局部规划(人工势能场法) & 智能路径规划(生物启发(蚁群&RVO) & 强化学习) 2019-04-30
D*算法 2019-04-30
强化学习(四) —— Actor-Critic演员评论家 & code 2019-04-30
RESTful API 2019-04-30
优化算法(四)——粒子群优化算法(PSO) 2019-04-30
数据挖掘与数据分析(三)—— 探索性数据分析EDA(多因子与复合分析) & 可视化(2)——回归分析(最小二乘法&决定系数&残差不相关)&主成分分析&奇异值分解 2019-04-30
数据在Oracle中的存储 2019-04-30
优化算法(五)—人工蜂群算法Artificial Bee Colony Algorithm(ABC) 2019-04-30
轨迹规划 trajectory planning 2019-04-30
AGV自动导引运输车 2019-04-30
Trie树(字典树) 2019-04-30
COMP7404 Machine Learing——Logistic Regression 2019-04-30
COMP7404 Machine Learing——Regularization(参数C) 2019-04-30
COMP7404 Machine Learing——KNN 2019-04-30
COMP7404 Machine Learing——SVM 2019-04-30
COMP7404 Machine Learing——Decision Tree & Random Forests 2019-04-30
COMP7404 Machine Learing——Hyperparameter Grid Search & Nested Cross-Validation 2019-04-30