Spring之@Transactional源码解析

https://blog.csdn.net/qq_38257958/article/details/136300388

前言

我们在日常开发的时候经常会用到组合注解,比如:@EnableTransactionManagement + @Transactional@EnableAsync + @Async@EnableAspectJAutoProxy + @Aspect。今天我们就来抽丝剥茧,揭开@Transactional注解的神秘面纱

@EnableTransactionManagement注解的作用

当我们看到类似@Enablexxx这样的注解,一般源码中都会存在@Import注解。@Import注解在Spring的解析阶段有着十分重要的地位,Spring的一个重要的扩展点。其注入的class一般继承ImportSelectorImportBeanDefinitionRegistrar接口,作用分别如下

  • 继承ImportSelector接口:selectImports方法返回的类名数组会被解析成bean
  • 继承ImportBeanDefinitionRegistrar接口:会在解析阶段执行registerBeanDefinitions方法

Spring@ComponentScan@Import@PropertySource@Bean等注解的解析流程,可以看这篇博文Spring之ConfigurationClassPostProcessor解析流程

1.@EnableTransactionManagement注解源码

https://i-blog.csdnimg.cn/blog_migrate/f2ad3505c5a552df007ce399c6bcc50f.png

https://i-blog.csdnimg.cn/blog_migrate/911dd194b50ebb8b409da15512cfb305.png https://i-blog.csdnimg.cn/blog_migrate/497df50121d49a88ff7943a95041eeb8.png

通过上面的源码,我们简单理论分析

@EnableTransactionManagement注解会注入一个类型为TransactionManagementConfigurationSelectorclass,class的父类实现selectImports方法,父类方法又会调用子类的同名方法。根据上文中阐述的@Import注解的作用,此时Spring容器中多了两个BeanDefinition:一个beanClassAutoProxyRegistrar,另一个beanClassProxyTransactionManagementConfiguration

结论1:@EnableTransactionManagement注解会import一个类型为TransactionManagementConfigurationSelectorclass,class实现ImportSelector接口,其接口方法返回[AutoProxyRegistrar,ProxyTransactionManagementConfiguration]类名数组,Spring容器在后期会存在beanClassAutoProxyRegistrarProxyTransactionManagementConfiguration的两个bean

2.AutoProxyRegistrar源码

https://i-blog.csdnimg.cn/blog_migrate/f17952c2fec3074a8f92959ea149056a.png

AutoProxyRegistrar实现ImportBeanDefinitionRegistrar接口,所以会在Spring的解析阶段执行registerBeanDefinitions方法,我们重点关注截图框住的方法,它会注入一个beanClassInfrastructureAdvisorAutoProxyCreatorBeanDefinitionInfrastructureAdvisorAutoProxyCreatorBeanPostProcessor(后文简称bpp)的子类,它会在普通bean的生命周期bean进行一些干预,比如当前bpp就会在Spring执行bpppostProcessAfterInitialization方法的时候会对bean进行动态代理(这个我们后文分析)

结论2:AutoProxyRegistrar会注入一个类型为InfrastructureAdvisorAutoProxyCreatorbean

3.ProxyTransactionManagementConfiguration源码

https://i-blog.csdnimg.cn/blog_migrate/9eba41145ec0700623c34d343b5409cf.png

这个类比较简单,就是一个配置类,利用@Configuration + @Bean的组合,创建了几个bean

结论3:ProxyTransactionManagementConfiguration会注入beanClassBeanFactoryTransactionAttributeSourceAdvisorTransactionAttributeSourceTransactionInterceptor的三个bean

小结

@EnableTransactionManagement注解会import一个实现ImportSelector接口的类,import的类会注入两个bean(beanClass分别为AutoProxyRegistrarProxyTransactionManagementConfiguration),其中AutoProxyRegistrar会进一步解析,然后注入一个类型为InfrastructureAdvisorAutoProxyCreatorbppProxyTransactionManagementConfiguration是一个配置类,会注入几个bean,协助@EnableTransactionManagement注解完成相关功能

2.什么是BeanPostProcessor

BeanPostProcessor从本质上说,它也是一个bean,不过它优先实例化,然后作用于普通bean。比如我们耳熟能详的属性注入、动态代理,都是BeanPostProcessor在不同阶段对普通bean进行的处理。

相关博文Spring之BeanPostProcessor

3.InfrastructureAdvisorAutoProxyCreator的作用

InfrastructureAdvisorAutoProxyCreator继承BeanPostProcessor,spring执行到postProcessAfterInitialization的时候会查找可以作用于当前beanAdvisors,如果存在符合条件的Advisors,则进行动态代理

https://i-blog.csdnimg.cn/blog_migrate/c2eb68a033aa0db485064e03ecd8968f.pnghttps://i-blog.csdnimg.cn/blog_migrate/7d86cbe4be6d690cc331f3e7c0175688.png

简单来说就是查找普通bean所属的class和方法上有没有@Transactional注解,如果满足条件则进行AOP动态代理。

详细查找过程Spring之AOP源码解析(下)

根据我们设置的参数,有可能进行JDK动态代理也有可能进行cglib动态代理,如果是JDK动态代理我们关注JdkDynamicAopProxy这个类,如果是cglib动态代理我们关注DynamicAdvisedInterceptor这个类。不管是什么动态代理都会有一个field(advised),这个参数存储了可以作用于当前beanAdvisors,每个Advisor都有一个advice对象(MethodInterceptor的父接口),Spring会将这些advice串成一个拦截器链,链式调用各个拦截器的invoke方法,我们画图演示流程

PS:有兴趣的小伙伴可以把我写的几篇关于AOP的文章都阅读一下,可以更好的帮助我们理解这篇博文。

https://i-blog.csdnimg.cn/blog_migrate/3b3c4ea8e64350749623f842a042f89d.png

 4.TransactionInterceptor源码

主要关注其invoke方法,invoke方法主要调用了invokeWithinTransaction方法

主体流程
  1.     protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
  2.                                              final TransactionAspectSupport.InvocationCallback invocation) throws Throwable {
  3.         // 上文分析的ProxyTransactionManagementConfiguration注入的三个bean之一(AnnotationTransactionAttributeSource)
  4.         TransactionAttributeSource tas = getTransactionAttributeSource();
  5.         // 1.从类上查找@Transactional注解
  6.         // 2.从方法上查找@Transactional注解
  7.         // 3.将步骤12查找到的@Transactional注解进行解析,构建成TransactionAttribute对象(RuleBasedTransactionAttribute)
  8.         final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
  9.         // 1.如果txAttrnull或者beanFactorynull,返回注入的TransactionManager
  10.         // 2.如果txAttr.getQualifier不为null(Transactional注解的value属性值),则从beanFactory获取配置的bean返回
  11.         // 3.如果注入了transactionManagerBeanName则从beanFactory获取bean返回
  12.         // 4.如果没注入TransactionManager,就从beanFactory获取classPlatformTransactionManagerbean返回
  13.         final TransactionManager tm = determineTransactionManager(txAttr);
  14.         // 省略webFlux相关代码
  15.         PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
  16.         final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
  17.         if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
  18.             // 开启事务(如果需要)
  19.             TransactionAspectSupport.TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
  20.             Object retVal;
  21.             try {
  22.                 // 执行下一个拦截器的invoke方法或者目标方法
  23.                 retVal = invocation.proceedWithInvocation();
  24.             } catch (Throwable ex) {
  25.                 // 处理异常
  26.                 completeTransactionAfterThrowing(txInfo, ex);
  27.                 throw ex;
  28.             } finally {
  29.                 // 解除线程和事务的绑定关系
  30.                 cleanupTransactionInfo(txInfo);
  31.             }
  32.             if (retVal != null && vavrPresent && TransactionAspectSupport.VavrDelegate.isVavrTry(retVal)) {
  33.                 // Set rollback-only in case of Vavr failure matching our rollback rules...
  34.                 TransactionStatus status = txInfo.getTransactionStatus();
  35.                 if (status != null && txAttr != null) {
  36.                     retVal = TransactionAspectSupport.VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
  37.                 }
  38.             }
  39.             // 提交事务
  40.             commitTransactionAfterReturning(txInfo);
  41.             return retVal;
  42.         }
  43.     }
  1. 基础数据准备
    1. 获取TransactionAttributeSource
    2. 获取TransactionAttribute
    3. 推断TransactionManager
  2. 开启事务
  3. 执行拦截器方法或者目标方法
  4. 处理异常(如果存在)
  5. 提交事务
事务同步管理器TransactionSynchronizationManager

https://i-blog.csdnimg.cn/blog_migrate/f1088efe7b96b17e3f771805a3ce85b5.png

这个类在@Transactional源码中起着重要作用,它不仅管理每个线程的资源和事务同步,也协助完成与mybatis的集成

开启事务

TransactionAspectSupport#createTransactionIfNecessary

https://i-blog.csdnimg.cn/blog_migrate/3c6f367b743ff3f8e9a1603b857403fb.png

AbstractPlatformTransactionManager#getTransaction

https://i-blog.csdnimg.cn/blog_migrate/c1cbb26bf0f26c4637df1a24390f55b0.png

AbstractPlatformTransactionManager#startTransaction

https://i-blog.csdnimg.cn/blog_migrate/ab4d1c60d63126d5fd7c2766420c3196.png

DataSourceTransactionManager#doBegin

  1.     protected void doBegin(Object transaction, TransactionDefinition definition) {
  2.         DataSourceTransactionManager.DataSourceTransactionObject txObject = (DataSourceTransactionManager.DataSourceTransactionObject) transaction;
  3.         Connection con = null;
  4.         try {
  5.             // 如果事务还没有获取Connection或者Connection还没标记为与事务同步
  6.             if (!txObject.hasConnectionHolder() ||
  7.                     txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
  8.                 // 获取Connection
  9.                 Connection newCon = obtainDataSource().getConnection();
  10.                 if (logger.isDebugEnabled()) {
  11.                     logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
  12.                 }
  13.                 // Connection标记为new Connection
  14.                 txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
  15.             }
  16.             // Connection没标记为与事务同步
  17.             txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
  18.             con = txObject.getConnectionHolder().getConnection();
  19.             // 设置事务的隔离级别
  20.             Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
  21.             txObject.setPreviousIsolationLevel(previousIsolationLevel);
  22.             txObject.setReadOnly(definition.isReadOnly());
  23.             // Connection的自动提交关闭
  24.             if (con.getAutoCommit()) {
  25.                 txObject.setMustRestoreAutoCommit(true);
  26.                 if (logger.isDebugEnabled()) {
  27.                     logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
  28.                 }
  29.                 con.setAutoCommit(false);
  30.             }
  31.             prepareTransactionalConnection(con, definition);
  32.             // 将事务标记为已激活
  33.             txObject.getConnectionHolder().setTransactionActive(true);
  34.             // 设置过期时间(如果手动设置了)
  35.             int timeout = determineTimeout(definition);
  36.             if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
  37.                 txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
  38.             }
  39.             // 如果是新连接,事务同步管理器同步资源
  40.             if (txObject.isNewConnectionHolder()) {
  41.                 TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
  42.             }
  43.         }
  44.         catch (Throwable ex) {
  45.             if (txObject.isNewConnectionHolder()) {
  46.                 DataSourceUtils.releaseConnection(con, obtainDataSource());
  47.                 txObject.setConnectionHolder(null, false);
  48.             }
  49.             throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
  50.         }
  51.     }

https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png

PS : Spring源码中存在很多模板模式,很多方法都是交给子类去实现,比如上文中的doGetTransaction,isExistingTransaction,doBegin等方法,如果我们不确定具体走的是那个子类,可以多多去debug看看。这里TransactionManager主要是子类DataSourceTransactionManager

注意:我们在mysql中开启事务,可以使用BEGINSTART TRANSACTION等,但是在源码中,我们并没有发现这样的sql语句。其实事务可以隐式开启,在上述doBegin方法的源码中,存在con.setAutoCommit(false)这样的方法,其实这就等价于执行sql语句set autocommit = OFF(隐式开启事务)

事务隔离级别
  • REQUIRED
  • SUPPORTS
  • MANDATORY
  • REQUIRES_NEW
  • NOT_SUPPORTED
  • NEVER
  • NESTED

通过源码整理事务处理流程

https://i-blog.csdnimg.cn/blog_migrate/232ffbc4c1dd1ac9dc9ddff5630daf87.png

异常处理

TransactionAspectSupport#completeTransactionAfterThrowing

https://i-blog.csdnimg.cn/blog_migrate/d908fdef865c41f5e72fa35d8730d089.png

https://i-blog.csdnimg.cn/blog_migrate/c1d8db659fa22cdd974cbcf10afdc627.png

我们在上文中指出该TransactionAttribute类型为RuleBasedTransactionAttribut,然后如果我们指定@Transactional注解的rollbackForrollbackForClassNamenoRollbackFornoRollbackForClassName属性,后期会被解析成RollbackRuleAttribute对象,相关源码明细可以查看AbstractFallbackTransactionAttributeSource(AnnotationTransactionAttributeSource的父类)getTransactionAttribute方法

case1:未指定rollbackForrollbackForClassNamenoRollbackFornoRollbackForClassName

当我们未指定上述四个属性,会调用super.rollbackOn的方法

https://i-blog.csdnimg.cn/blog_migrate/ddbd079590a84c84b4eb439dec95be1e.png

当我们未指定rollbackForrollbackForClassNamenoRollbackFornoRollbackForClassName属性的时候,事务只有在遇到RuntimeException异常或者Error的时候才会回滚

case2:指定rollbackForrollbackForClassNamenoRollbackFornoRollbackForClassName

当我们指定了四个属性中的一个或者多个,就会被解析成RollbackRuleAttribute(NoRollbackRuleAttribute),最后通过getDepth方法获取winner,如果winnerRollbackRuleAttribute则回滚事务,否则提交事务

https://i-blog.csdnimg.cn/blog_migrate/efadd7060aa947b1ce614820e2130c86.png

getDepth方法主要判断抛出的异常与指定的异常之间的关系

  • 如果抛出异常和指定异常一致,depth0
  • 如果抛出异常是指定异常子类,depth1(递归判断)
  • 如果抛出异常为Throwable,depth-1

depth值越小(大于0),优先级越高

触发器

https://i-blog.csdnimg.cn/blog_migrate/117663f736066775c3b02e1dc1c332fe.pnghttps://i-blog.csdnimg.cn/blog_migrate/fb1175889c78be68312432abbedc8251.png

不管最后事务是提交还是回滚,都会执行相应的触发器方法,我们可以利用这一特性,做一些扩展https://i-blog.csdnimg.cn/blog_migrate/ccd3d9fc0c3ca8f7b9e772b64db731b7.png

mybatis的整合

mybatis相关接口会被JDK动态代理,代理对象的类型是MapperProxy

相关博文@MapperScan源码解析

MapperProxy#invokehttps://i-blog.csdnimg.cn/blog_migrate/f83b26ce0ca731d0a874848902e6ac3f.png

PlainMethodInvoker#invokehttps://i-blog.csdnimg.cn/blog_migrate/8e77619ba3fe01ab07abb728b5e8cd5c.png MapperMethod#executehttps://i-blog.csdnimg.cn/blog_migrate/dc79c9a4e97cecd208496872ceecddad.png SqlSessionTemplate#selectOnehttps://i-blog.csdnimg.cn/blog_migrate/147f23a71efdf8aeeee3f73bedb7851e.png
SqlSessionInterceptor#invoke(sqlSessionProxy
是一个代理对象,所以会进入相应拦截器方法)https://i-blog.csdnimg.cn/blog_migrate/f8355908a13acd8ebc8a7bbaee1e516b.png SqlSessionUtils#getSqlSession https://i-blog.csdnimg.cn/blog_migrate/d2b1595edb6ecf6f9958fca37079a5c4.png

我们看到了我们熟悉的TransactionSynchronizationManager,mybatis执行sql的时候会从事务同步管理器里面获取resource,保证了同个事务里面的增删改查都是使用的同一个SqlSession

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值