通过@Transactional常见的失效场景带你通俗理解SpringAOP的原理

开门见山,先来说一下Spring AOP(Aspect-Oriented Programming,面向切面编程)的原理其实就是:在不修改原有代码的情况下,通过动态代理机制在运行时织入"增强逻辑"

以下是@Transactional常见的失效场景

1. 方法不是 public

2. 同一个类中的方法调用 (最常见)

3. 异常被 try-catch 捕获了

4. rollbackFor 设置不正确

我们从 同一个类中的方法调用导致的@Transactional失效 的场景来带大家理解SpringAOP,其他的失效场景相信大家看完这篇文章也能够自己独立的进行分析

typescript 复制代码
@Service
public class MyService {

    public void methodA() {
        // ...
        this.methodB(); // 事务会失效!
    }

    @Transactional
    public void methodB() {
        // ... 数据库操作 ...
    }
}

为什么在methodA()方法内部之间调用methodB()会导致@Transactional失效呢?

原因: methodA() 的调用 this.methodB() 是一个普通的 Java 对象内部调用,它直接指向了目标对象的原始方法,完全绕过了 Spring 的代理对象。没有经过代理,就无法执行开启事务等切面逻辑。

解释: 可以将SpringAOP想象成为MyService这栋楼的一个保安(代理对象 :一但发现一个增强点就会对整个类创建一个代理对象),每次楼外的人要进入createVoucherOrder办公室都会开启一个事务再放行,如果是楼内的两个办公室互相进出(也就是对象内部直接调用),此时就绕过了代理对象就失效了。

通过以上的解释想必大家能够大概知道SpringAOP大致的作用了,不过大家可能还是对于一些细节点还是想进行更深入的理解以下我会对上面所述的解释进行更深入的讲解。

为什么MyService这栋楼会有这个代理对象呢?

当 Spring 容器检测到一个 Bean(例如 MyService)的方法上(例如 methodB)有 @Transactional 注解时,它会为这个 Bean 创建一个代理对象

注:并不是每个Bean都有的,只是这个Bean中的methodB上有@Transactional,@Transactional 注解是通过AOP(面向切面编程)代理 来实现事务管理的,所以它会为这个 Bean 创建一个代理对象

说了这么多,怎么解决目前这个失效的问题呢?

很简单,既然它绕过了代理,我们就想办法让它还得是从代理通过

typescript 复制代码
@Service
public class MyService {

    public void methodA() {
        // ...
        //this.methodB(); // 事务会失效!
       //获取当前类的 Spring AOP 代理对象
       MyService proxy = (MyService) AopContext.currentProxy();
       proxy.methodB();
    }

    @Transactional
    public void methodB() {
        // ... 数据库操作 ...
    }
}

通过对以上问题的解决想必大家知道代理的作用了,不过大家可能还是不知道为什么@Transactional能为这个方法开启一个"数据库事务"

开启事务等切面逻辑是什么意思?

当调用methodB()方法时,Spring的事务切面会通过"环绕通知"织入逻辑,效果如下:

csharp 复制代码
// --- Spring AOP 动态生成的代理代码 (伪代码) ---
public Result methodB_AOP_Proxy() {
    // 1. @Before: 开启事务
    TransactionManager.beginTransaction(); 
    try {
        // 2. 调用原始的 methodB() 方法
        Result result = original.methodB();

        // 3. @AfterReturning: 提交事务
        TransactionManager.commit(); 
        return result;
    } catch (Exception e) {
        // 4. @AfterThrowing: 回滚事务
        TransactionManager.rollback();
        throw e; // 抛出异常
    }
}

至此就说明的SpringAOP的原理:在不修改原有代码的情况下,通过动态代理机制在运行时织入"增强逻辑",以上的代码就是它生成的增强逻辑,并且不会干扰到我们在项目中的业务代码。

不过大家对于 通过"环绕通知"织入逻辑 这个操作可能会有些困惑,我再来解释一下

在此之前我们需要拆解一下AOP的几个核心概念:

  1. 切面 (Aspect)

    • 是什么 :就是我们要增加的"额外功能",在这里就是事务管理 。这个功能包括在方法开始前"开启事务",在方法成功结束后"提交事务",在方法抛出异常时"回滚事务"。定义了要做什么事在哪里做
    • 在项目中 :Spring框架已经内置了事务管理的切面,我们只需要通过@Transactional注解来启用它。
  2. 连接点 (Join Point)

    • 是什么 :指程序中可以被增强的地方。在Spring中,最常见的就是方法的执行。
    • 在项目中methodB方法本身就是一个连接点。理论上,任何方法都可以是一个连接点。
  3. 切点 (Pointcut)

    • 是什么 :它是一个规则,用来精确地定位哪些连接点(哪些方法)需要被切面增强。
    • 在项目中@Transactional注解本身就定义了一个切点,它的规则就是:"所有被 @Transactional 标记的方法"。所以,Spring AOP知道要去增强methodB方法,而不会去管其他没有这个注解的方法。
  4. 通知 (Advice)

    • 是什么 :指切面在切点(具体的方法)上要执行的具体操作。通知有几种类型:

      • @Before:在方法执行前执行(例如:开启事务)。
      • @AfterReturning:在方法成功返回后执行(例如:提交事务)。
      • @AfterThrowing:在方法抛出异常后执行(例如:回滚事务)。
      • @Around:环绕通知,最强大,可以完全控制方法的执行前后,上面三种都可以用它实现。
    • 定义了什么时候做什么

    • 在项目中 :当调用methodB()方法时,Spring的事务切面会通过"环绕通知"织入逻辑

通过对于以上概念的解释相信大家对于织入逻辑的这个操作会有更深的理解,现在我画一张图来让大家更形象的看明白这个流程

以上就是所有的内容,希望能为各位带来帮助

相关推荐
over6972 小时前
从 LLM 到全栈 Agent:MCP 协议 × RAG 技术如何重构 AI 的“做事能力”
面试·llm·mcp
SuperEugene3 小时前
Vue状态管理扫盲篇:如何设计一个合理的全局状态树 | 用户、权限、字典、布局配置
前端·vue.js·面试
Sailing5 小时前
🚀 别再乱写 16px 了!CSS 单位体系已经进入“计算时代”,真正的响应式布局
前端·css·面试
SuperEugene8 小时前
Vue状态管理扫盲篇:Vuex 到 Pinia | 为什么大家都在迁移?核心用法对比
前端·vue.js·面试
Hilaku8 小时前
我会如何考核一个在简历里大谈 AI 提效的高级前端?
前端·javascript·面试
前端Hardy8 小时前
别再用 $emit 满天飞了!Vue 3 组件通信的 4 种正确姿势,第 3 种 90% 的人不知道
前端·vue.js·面试
我叫黑大帅9 小时前
前端如何利用 GitHub Actions 自动构建并发布到 GitHub Pages?
前端·面试·github
我叫黑大帅9 小时前
前端总说的防抖与节流到底是什么?
前端·javascript·面试
掘金安东尼9 小时前
从平面到空间:用 React Three Fiber 构建 3D 产品网格
前端·javascript·面试
swipe9 小时前
#用这 9 个浏览器 API,我把页面从“卡成 PPT”救回到 90+(每个都有能直接抄的例子)
前端·javascript·面试