• Spring之@Transactional源码解析


    前言

    我们在日常开发的时候经常会用到组合注解,比如:@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解析流程icon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/134761961?spm=1001.2014.3001.5501

    1.@EnableTransactionManagement注解源码

     

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

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

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

    2.AutoProxyRegistrar源码

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

    结论2:AutoProxyRegistrar会注入一个类型为InfrastructureAdvisorAutoProxyCreator的bean

    3.ProxyTransactionManagementConfiguration源码

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

    结论3:ProxyTransactionManagementConfiguration会注入beanClass为BeanFactoryTransactionAttributeSourceAdvisor、TransactionAttributeSource、TransactionInterceptor的三个bean

    小结

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

    2.什么是BeanPostProcessor

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

    详情阅读下方链接博文
    Spring之BeanPostProcessoricon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/134753005?spm=1001.2014.3001.5502

    3.InfrastructureAdvisorAutoProxyCreator的作用

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

    具体查找过程可以查看下方链接博文。简单来说就是查找普通bean所属的class和方法上有没有@Transactional注解,如果满足条件则进行AOP动态代理。Spring之AOP源码解析(下)icon-default.png?t=N7T8https://blog.csdn.net/qq_38257958/article/details/136182213?spm=1001.2014.3001.5502

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

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

     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.如果txAttr为null或者beanFactory为null,返回注入的TransactionManager
    10. // 2.如果txAttr.getQualifier不为null(即Transactional注解的value属性值),则从beanFactory获取配置的bean返回
    11. // 3.如果注入了transactionManagerBeanName则从beanFactory获取bean返回
    12. // 4.如果没注入TransactionManager,就从beanFactory获取class为PlatformTransactionManager的bean返回
    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

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

    开启事务

    TransactionAspectSupport#createTransactionIfNecessary

    AbstractPlatformTransactionManager#getTransaction

    AbstractPlatformTransactionManager#startTransaction

    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. }

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

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

    事务隔离级别

    • REQUIRED
    • SUPPORTS
    • MANDATORY
    • REQUIRES_NEW
    • NOT_SUPPORTED
    • NEVER
    • NESTED

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

    异常处理

    TransactionAspectSupport#completeTransactionAfterThrowing

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

    case1:未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

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

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

    case2:指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName

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

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

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

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

    触发器

    不管最后事务是提交还是回滚,都会执行相应的触发器方法,我们可以利用这一特性,做一些扩展

    与mybatis的整合

    mybatis相关接口会被JDK动态代理,代理对象的类型是MapperProxy。感兴趣小伙伴可以阅读我之前的博文 《@MapperScan源码解析

    MapperProxy#invoke

    PlainMethodInvoker#invokeMapperMethod#executeSqlSessionTemplate#selectOne
    SqlSessionInterceptor#invoke(sqlSessionProxy是一个代理对象,所以会进入相应拦截器方法)SqlSessionUtils#getSqlSession 

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

  • 相关阅读:
    【SpringBoot】70、SpringBoot实现MySQL数据库自动备份管理系统
    Spring aware接口理解
    Git详细介绍 -入门到实战万字篇(上)
    PAT 1027 Colors in Mars
    Redis-数据库和缓存如何保证一致性?
    盘点有趣的人工智能开源项目一
    Flutter splash 屏幕
    【已解决】胎教级别FontDetection在本机电脑指导跑通
    Java8 Stream生成流 generate iterate IntStream
    kafka 使用thrift序列化对象
  • 原文地址:https://blog.csdn.net/qq_38257958/article/details/136300388