Spring事务专题(五)聊聊Spring事务到底是如何实现的(1)

简介: Spring事务专题(五)聊聊Spring事务到底是如何实现的(1)

前言


本专题大纲:

aHR0cHM6Ly9naXRlZS5jb20vd3hfY2MzNDdiZTY5Ni9ibG9nSW1hZ2UvcmF3L21hc3Rlci9pbWFnZS0yMDIwMDgwOTE4MjA1MjUyMC5wbmc.png

本文为本专题倒数第二篇文章。

在上篇文章中我们一起学习了Spring中的事务抽象机制以及动手模拟了一下Spring中的事务管理机制,那么本文我们就通过源码来分析一下Spring中的事务管理到底是如何实现的,本文将选用Spring5.2.x版本。


从@EnableTransactionManagement开始


Spring事务管理的入口就是@EnableTransactionManagement注解,所以我们直接从这个注解入手,其源码如下:

public @interface EnableTransactionManagement {
  // 是否使用cglib代理,默认是jdk代理
  boolean proxyTargetClass() default false;
    // 使用哪种代理模式,Spring AOP还是AspectJ
  AdviceMode mode() default AdviceMode.PROXY;
    // 为了完成事务管理,会向容器中添加通知
    // 这个order属性代表了通知的执行优先级
    // 默认是最低优先级
  int order() default Ordered.LOWEST_PRECEDENCE;
}

需要注意的是,@EnableTransactionManagement的proxyTargetClass会影响Spring中所有通过自动代理生成的对象。如果将proxyTargetClass设置为true,那么意味通过@EnableAspectJAutoProxy所生成的代理对象也会使用cglib进行代理。关于@EnableTransactionManagement跟@EnableAspectJAutoProxy混用时的一些问题等我们在对@EnableTransactionManagement有一定了解后再专门做一个比较,现在我们先来看看这个注解到底在做了什么?

aHR0cHM6Ly9naXRlZS5jb20vd3hfY2MzNDdiZTY5Ni9ibG9nSW1hZ2UvcmF3L21hc3Rlci8lRTQlQkElOEIlRTUlOEElQTElRTYlQjMlQTglRTglQTclQTMlRTUlODglODYlRTYlOUUlOTAucG5n.png

从上图中可以看出这个注解做的就是向容器中注册了AutoProxyRegistrar跟一个ProxyTransactionManagementConfiguration(这里就不考虑AspectJ了,我们平常都是使用SpringAOP),


AutoProxyRegistrar用于开启自动代理,其源码如下:


AutoProxyRegistrar分析


这个类实现了ImportBeanDefinitionRegistrar,它的作用是向容器中注册别的BeanDefinition,我们直接关注它的registerBeanDefinitions方法即可

//  AnnotationMetadata,代表的是AutoProxyRegistrar的导入类的元信息
// 既包含了类元信息,也包含了注解元信息
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    boolean candidateFound = false;
    // 获取@EnableTransactionManagement所在配置类上的注解元信息
    Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
    // 遍历注解
    for (String annType : annTypes) {
        // 可以理解为将注解中的属性转换成一个map
        AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
        if (candidate == null) {
            continue;
        }
        // 直接从map中获取对应的属性
        Object mode = candidate.get("mode");
        Object proxyTargetClass = candidate.get("proxyTargetClass");
        // mode,代理模型,一般都是SpringAOP
        // proxyTargetClass,是否使用cglib代理
        if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
            Boolean.class == proxyTargetClass.getClass()) {
            // 注解中存在这两个属性,并且属性类型符合要求,表示找到了合适的注解
            candidateFound = true;
            // 实际上会往容器中注册一个InfrastructureAdvisorAutoProxyCreator
            if (mode == AdviceMode.PROXY) {
                AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                if ((Boolean) proxyTargetClass) {
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                    return;
                }
            }
        }
    }
    // ......
}

@EnableTransactionManagement跟@EnableAspectJAutoProxy


如果对AOP比较了解的话,那么应该知道@EnableAspectJAutoProxy注解也向容器中注册了一个能实现自动代理的bd,那么当@EnableAspectJAutoProxy跟@EnableTransactionManagement同时使用会有什么问题吗?答案大家肯定知道,不会有问题,那么为什么呢?我们查看源码会发现,@EnableAspectJAutoProxy最终调用的是


AopConfigUtils#registerAspectJAnnotationAutoProxyCreatorIfNecessary,其源码如下

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}

@EnableTransactionManagement最终调用的是,AopConfigUtils#registerAutoProxyCreatorIfNecessary,其源码如下

public static BeanDefinition registerAutoProxyCreatorIfNecessary(
    BeanDefinitionRegistry registry, @Nullable Object source) {
    return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
}

它们最终都会调用registerOrEscalateApcAsRequired方法,只不过传入的参数不一样而已,一个是AnnotationAwareAspectJAutoProxyCreator,另一个是InfrastructureAdvisorAutoProxyCreator。


registerOrEscalateApcAsRequired源码如下:

private static BeanDefinition registerOrEscalateApcAsRequired(
    Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
    if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
        BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
        if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
            // 当前已经注册到容器中的Bean的优先级
            int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
            // 当前准备注册到容器中的Bean的优先级
            int requiredPriority = findPriorityForClass(cls);
            // 谁的优先级大就注册谁,AnnotationAwareAspectJAutoProxyCreator是最大的
            // 所以AnnotationAwareAspectJAutoProxyCreator会覆盖别的Bean
            if (currentPriority < requiredPriority) {
                apcDefinition.setBeanClassName(cls.getName());
            }
        }
        return null;
    }
  // 注册bd
    RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
    beanDefinition.setSource(source);
    beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
    beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
    registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
    return beanDefinition;
}

InfrastructureAdvisorAutoProxyCreator跟AnnotationAwareAspectJAutoProxyCreator的优先级是如何定义的呢?我们来看看AopConfigUtils这个类中的一个静态代码块

static {
    APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
    APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
}

实际上它们的优先级就是在APC_PRIORITY_LIST这个集合中的下标,下标越大优先级越高,所以AnnotationAwareAspectJAutoProxyCreator的优先级最高,所以AnnotationAwareAspectJAutoProxyCreator会覆盖InfrastructureAdvisorAutoProxyCreator,那么这种覆盖会不会造成问题呢?答案肯定是不会的,因为你用了这么久了也没出过问题嘛~那么再思考一个问题,为什么不会出现问题呢?这是因为InfrastructureAdvisorAutoProxyCreator只会使用容器内部定义的Advisor,但是AnnotationAwareAspectJAutoProxyCreator会使用所有实现了Advisor接口的通知,也就是说AnnotationAwareAspectJAutoProxyCreator的作用范围大于InfrastructureAdvisorAutoProxyCreator,因此这种覆盖是没有问题的。限于篇幅原因这个问题我不做详细解答了,有兴趣的同学可以看下两个类的源码。


@EnableTransactionManagement除了注册了一个AutoProxyRegistrar外,还向容器中注册了一个ProxyTransactionManagementConfiguration。


那么这个ProxyTransactionManagementConfiguration有什么作用呢?


如果大家对我文章的风格有一些了解的话就会知道,分析一个类一般有两个切入点

1.它的继承关系

2.它提供的API

大家自己在阅读源码时也可以参考这种思路,分析一个类的继承关系可以让我们了解它从抽象到实现的过程,即使不去细看API也能知道它的大体作用。仅仅知道它的大致作用是不够的,为了更好了解它的细节我们就需要进一步去探索它的具体实现,也就是它提供的API。这算是我看了这么就源码的一点心得,正好想到了所以在这里分享下,之后会专门写一篇源码心得的文章


ProxyTransactionManagementConfiguration分析


继承关系


aHR0cHM6Ly9naXRlZS5jb20vd3hfY2MzNDdiZTY5Ni9ibG9nSW1hZ2UvcmF3L21hc3Rlci9Qcm94eVRyYW5zYWN0aW9uTWFuYWdlbWVudENvbmZpZ3VyYXRpb24ucG5n.png

这个类的继承关系还是很简单的,只有一个父类

AbstractTransactionManagementConfiguration


AbstractTransactionManagementConfiguration


源码如下:

@Configuration
public abstract class AbstractTransactionManagementConfiguration implements ImportAware {
  @Nullable
  protected AnnotationAttributes enableTx;
  @Nullable
  protected TransactionManager txManager;
  // 这个方法就是获取@EnableTransactionManagement的属性
    // importMetadata:就是@EnableTransactionManagement这个注解所在类的元信息
  @Override
  public void setImportMetadata(AnnotationMetadata importMetadata) {
        // 将EnableTransactionManagement注解中的属性对存入到map中
        // AnnotationAttributes实际上就是个map
    this.enableTx = AnnotationAttributes.fromMap(       importMetadata.getAnnotationAttributes(EnableTransactionManagement.class.getName(), false));
        // 这里可以看到,限定了导入的注解必须使用@EnableTransactionManagement
        if (this.enableTx == null) {
            throw new IllegalArgumentException(
                "@EnableTransactionManagement is not present on importing class " + importMetadata.getClassName());
        }
  }
    // 我们可以配置TransactionManagementConfigurer
    // 通过TransactionManagementConfigurer向容器中注册一个事务管理器
    // 一般不会这么使用,更多的是通过@Bean的方式直接注册
  @Autowired(required = false)
  void setConfigurers(Collection<TransactionManagementConfigurer> configurers) {
    // .....
    TransactionManagementConfigurer configurer = configurers.iterator().next();
    this.txManager = configurer.annotationDrivenTransactionManager();
  }
    // 向容器中注册一个TransactionalEventListenerFactory
    // 这个类用于处理@TransactionalEventListener注解
    // 可以实现对事件的监听,并且在事务的特定阶段对事件进行处理
  @Bean(name = TransactionManagementConfigUtils.TRANSACTIONAL_EVENT_LISTENER_FACTORY_BEAN_NAME)
  @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
  public static TransactionalEventListenerFactory transactionalEventListenerFactory() {
    return new TransactionalEventListenerFactory();
  }
}

TransactionalEventListenerFactory


上面的代码中大家可能比较不熟悉的就是TransactionalEventListenerFactory,这个类主要是用来处理@TransactionalEventListener注解的,我们来看一个实际使用的例子

@Component
public class DmzListener {
    // 添加一个监听器
    // phase = TransactionPhase.AFTER_COMMIT意味着这个方法在事务提交后执行
  @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
  public void listen(DmzTransactionEvent transactionEvent){
    System.out.println("事务已提交");
  }
}
// 定义一个事件
public class DmzTransactionEvent extends ApplicationEvent {
  public DmzTransactionEvent(Object source) {
    super(source);
  }
}
@Component
public class DmzService {
  @Autowired
  ApplicationContext applicationContext;
    // 一个需要进行事务管理的方法
  @Transactional
  public void invokeWithTransaction() {
        // 发布一事件
    applicationContext.publishEvent(new DmzTransactionEvent(this));
        // 以一条sout语句提代sql执行过程
    System.out.println("sql invoked");
  }
}
// 测试方法
public class Main {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ac =
        new AnnotationConfigApplicationContext(Config.class);
    DmzService dmzService = ac.getBean(DmzService.class);
    dmzService.invokeWithTransaction();
  }
}
// 最后程序会按顺序输出
// sql invoked
// 事务已提交

通过上面的例子我们可以看到,虽然我们在invokeWithTransaction方法中一开始就发布了一个事件,但是监听事件的方法却是在invokeWithTransaction才执行的,正常事件的监听是同步的,假设我们将上述例子中的@TransactionalEventListener注解替换成为@EventListener注解,如下:

@Component
public class DmzListener {
    // 添加一个监听器
  @EventListener
  public void listen(DmzTransactionEvent transactionEvent){
    System.out.println("事务已提交");
  }
}

这个时候程序的输出就会是

// 事务已提交
// sql invoked

那么@TransactionalEventListener注解是实现这种看似异步(实际上并不是)的监听方式的呢?

aHR0cHM6Ly9naXRlZS5jb20vd3hfY2MzNDdiZTY5Ni9ibG9nSW1hZ2UvcmF3L21hc3Rlci9UcmFuc2FjdGlvbmFsRXZlbnRMaXN0ZW5lckZhY3RvcnklRTglQjAlODMlRTclOTQlQTglRTklOTMlQkUucG5n.png

大家按照上面这个调用链可以找到这么一段代码

aHR0cHM6Ly9naXRlZS5jb20vd3hfY2MzNDdiZTY5Ni9ibG9nSW1hZ2UvcmF3L21hc3Rlci9pbWFnZS0yMDIwMDgxMDE4MDYxNjI4NC5wbmc (1).png

通过上面的代码,我们可以发现最终会调用到TransactionalEventListenerFactory的createApplicationListener方法,通过这个方法创建一个事件监听器然后添加到容器中,createApplicationListener方法源码如下:

public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
    return new ApplicationListenerMethodTransactionalAdapter(beanName, type, method);
}

就是创建了一个ApplicationListenerMethodTransactionalAdapter,这个类本身就是一个事件监听器(实现了ApplicationListener接口)。我们直接关注它的事件监听方法,也就是onApplicationEvent方法,其源码如下:

@Override
public void onApplicationEvent(ApplicationEvent event) {
    // 激活了同步,并且真实存在事务
    if (TransactionSynchronizationManager.isSynchronizationActive() &&
        TransactionSynchronizationManager.isActualTransactionActive()) {
        // 实际上是依赖事务的同步机制实现的事件监听
        TransactionSynchronization transactionSynchronization = createTransactionSynchronization(event);
        TransactionSynchronizationManager.registerSynchronization(transactionSynchronization);
    }
    // 在没有开启事务的情况下是否处理事件
    else if (this.annotation.fallbackExecution()) {
        // ....
        // 如果注解中的fallbackExecution为true,意味着没有事务开启的话
        // 也会执行监听逻辑
        processEvent(event);
    }
    else {
       // ....
    }
}

到这一步逻辑已经清楚了,@TransactionalEventListener所标注的方法在容器启动时被解析成了一个ApplicationListenerMethodTransactionalAdapter,这个类本身就是一个事件监听器,当容器中的组件发布了一个事件后,如果事件匹配,会进入它的onApplicationEvent方法,这个方法并没有直接执行我们所定义的监听逻辑,而是给当前事务注册了一个同步的行为,当事务到达某一个阶段时,这个行为会被触发。通过这种方式,实现一种伪异步。实际上注册到事务的的同步就是TransactionSynchronizationEventAdapter,这个类的源码非常简单,这里就单独取它一个方法看下

// 这个方法会在事务提交前执行
public void beforeCommit(boolean readOnly) {
    // 在执行时会先判断在@TransactionalEventListener注解中定义的phase是不是BEFORE_COMMIT
    // 如果不是的话,什么事情都不做
   if (this.phase == TransactionPhase.BEFORE_COMMIT) {
      processEvent();
   }
}

别看上面这么多内容,到目前为止我们还是只对ProxyTransactionManagementConfiguration的父类做了介绍,接下来我们就来看看ProxyTransactionManagementConfiguration自身做了什么事情。

相关文章
|
8月前
|
SQL Java 关系型数据库
Spring事务传播机制:7种姿势教你玩转"事务接力赛"
事务传播机制是Spring框架中用于管理事务行为的重要概念,它决定了在方法调用时事务如何传递与执行。通过7种传播行为,开发者可以灵活控制事务边界,适应不同业务场景。例如:REQUIRED默认加入或新建事务,REQUIRES_NEW独立开启新事务,NESTED支持嵌套回滚等。合理使用传播机制不仅能保障数据一致性,还能提升系统性能与健壮性。掌握这“七种人格”,才能在复杂业务中游刃有余。
|
Java Spring
Spring中事务失效的场景
因为Spring事务是基于代理来实现的,所以某个加了@Transactional的⽅法只有是被代理对象调⽤时, 那么这个注解才会⽣效 , 如果使用的是被代理对象调用, 那么@Transactional会失效 同时如果某个⽅法是private的,那么@Transactional也会失效,因为底层cglib是基于⽗⼦类来实现 的,⼦类是不能重载⽗类的private⽅法的,所以⽆法很好的利⽤代理,也会导致@Transactianal失效 如果在业务中对异常进行了捕获处理 , 出现异常后Spring框架无法感知到异常, @Transactional也会失效
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——常见问题总结
本文总结了Spring Boot中使用事务的常见问题,虽然通过`@Transactional`注解可以轻松实现事务管理,但在实际项目中仍有许多潜在坑点。文章详细分析了三个典型问题:1) 异常未被捕获导致事务未回滚,需明确指定`rollbackFor`属性;2) 异常被try-catch“吃掉”,应避免在事务方法中直接处理异常;3) 事务范围与锁范围不一致引发并发问题,建议调整锁策略以覆盖事务范围。这些问题看似简单,但一旦发生,排查难度较大,因此开发时需格外留意。最后,文章提供了课程源代码下载地址,供读者实践参考。
385 0
|
Java 关系型数据库 数据库
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——Spring Boot 事务配置
本文介绍了 Spring Boot 中的事务配置与使用方法。首先需要导入 MySQL 依赖,Spring Boot 会自动注入 `DataSourceTransactionManager`,无需额外配置即可通过 `@Transactional` 注解实现事务管理。接着通过创建一个用户插入功能的示例,展示了如何在 Service 层手动抛出异常以测试事务回滚机制。测试结果表明,数据库中未新增记录,证明事务已成功回滚。此过程简单高效,适合日常开发需求。
1764 0
|
Java 数据库 微服务
微服务——SpringBoot使用归纳——Spring Boot事务配置管理——事务相关
本文介绍Spring Boot事务配置管理,阐述事务在企业应用开发中的重要性。事务确保数据操作可靠,任一异常均可回滚至初始状态,如转账、购票等场景需全流程执行成功才算完成。同时,事务管理在Spring Boot的service层广泛应用,但根据实际需求也可能存在无需事务的情况,例如独立数据插入操作。
336 0
|
9月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
1123 1
|
12月前
|
人工智能 Java 数据库连接
Spring事务失效场景
本文深入探讨了Spring框架中事务管理可能失效的几种常见场景及解决方案,包括事务方法访问级别不当、方法内部自调用、错误的异常处理、事务管理器或数据源配置错误、数据库不支持事务以及不合理的事务传播行为或隔离级别。通过合理配置和正确使用`@Transactional`注解,开发者可以有效避免这些问题,确保应用的数据一致性和完整性。
1001 10
|
11月前
|
Java 关系型数据库 MySQL
【Spring】【事务】初学者直呼学会了的Spring事务入门
本文深入解析了Spring事务的核心概念与使用方法。Spring事务是一种数据库事务管理机制,通过确保操作的原子性、一致性、隔离性和持久性(ACID),维护数据完整性。文章详细讲解了声明式事务(@Transactional注解)和编程式事务(TransactionTemplate、PlatformTransactionManager)的区别与用法,并探讨了事务传播行为(如REQUIRED、REQUIRES_NEW等)及隔离级别(如READ_COMMITTED、REPEATABLE_READ)。
754 1
|
SQL Java 数据库连接
Spring中的事务是如何实现的
1. Spring事务底层是基于数据库事务和AOP机制的 2. ⾸先对于使⽤了@Transactional注解的Bean,Spring会创建⼀个代理对象作为Bean 3. 当调⽤代理对象的⽅法时,会先判断该⽅法上是否加了@Transactional注解 4. 如果加了,那么则利⽤事务管理器创建⼀个数据库连接 5. 并且修改数据库连接的autocommit属性为false,禁⽌此连接的⾃动提交,这是实现Spring事务⾮ 常重要的⼀步 6. 然后执⾏当前⽅法,⽅法中会执⾏sql 7. 执⾏完当前⽅法后,如果没有出现异常就直接提交事务 8. 如果出现了异常,并且这个异常是需要回滚的就会回滚事务
|
JavaScript Java 开发者
Spring事务失效,常见的情况有哪些?
本文总结了Spring事务失效的7种常见情况,包括未启用事务管理功能、方法非public类型、数据源未配置事务管理器、自身调用问题、异常类型错误、异常被吞以及业务和事务代码不在同一线程中。同时提供了两种快速定位事务相关Bug的方法:通过查看日志(设置为debug模式)或调试代码(在TransactionInterceptor的invoke方法中设置断点)。文章帮助开发者更好地理解和解决Spring事务中的问题。
675 7