从 AOP 到代理:Spring 事务注解是如何生效的?

原文来自于:zha-ge.cn/java/122

从 AOP 到代理:Spring 事务注解是如何生效的?

第一次用 @Transactional 的时候,我的心情是这样的:

"太神奇了!啥也不用写,就能自动回滚事务!Spring 也太懂我了吧~"

结果没过几天,Bug 就给我一记重拳------明明加了 @Transactional,数据却压根没回滚。

这让我第一次意识到: @Transactional 并不是魔法,它的生效背后是一整套 AOP + 代理的机制在兜底。

今天,我们就来扒一扒这件"看似简单"的事背后,Spring 究竟做了多少骚操作。


那个"明明加了注解却不回滚"的坑

有一次我在做订单模块,写了个非常简单的服务:

typescript 复制代码
@Service
public class OrderService {

    @Transactional
    public void createOrder() {
        orderRepository.save(...);
        throw new RuntimeException("测试回滚");
    }
}

我以为抛出异常后事务肯定会回滚,结果------数据进了库,没回滚

我当场人都傻了。

最后定位到罪魁祸首:我是在同一个类里调用了这个方法

csharp 复制代码
public void process() {
    createOrder(); // 自调用
}

这次事件给我上了一课:@Transactional 能否生效,和"有没有代理"息息相关


Step 1:事务注解只是个"标记"

我们都知道,@Transactional 是用来声明事务的,比如:

typescript 复制代码
@Transactional
public void saveUser() { ... }

但注解本身不做任何实事,它只是个元数据标记。真正"让它生效"的,是 Spring 容器在解析 Bean 时,扫描到这个注解,然后做了一系列"增强"的操作。


Step 2:AOP 出场,事务被"织"进去

当 Spring 扫描到一个带有 @Transactional 的类或方法时,它会做什么?

👉 它不会直接改你的方法逻辑,而是:用 AOP 机制为这个 Bean 生成一个代理对象

这个代理对象在方法执行前后,会织入一段"事务处理逻辑":

markdown 复制代码
[代理方法执行流程]

1. 开启事务
2. 执行目标方法(你的业务逻辑)
3. 如果没有异常 → 提交事务
4. 如果抛出异常 → 回滚事务

所以,当你调用这个方法的时候,其实不是在直接调你的原方法,而是在调一个"包装了事务逻辑"的代理。


Step 3:代理是怎么生成的?

Spring 有两种常见的代理方式:

  • JDK 动态代理:如果你的 Bean 实现了接口,Spring 会创建一个基于接口的代理对象。
  • CGLIB 动态代理:如果没有接口,Spring 会为你的类生成一个子类代理。

比如:

java 复制代码
public interface OrderService {
    void createOrder();
}

@Service
public class OrderServiceImpl implements OrderService {
    @Transactional
    public void createOrder() { ... }
}

容器启动时,Spring 不直接注入 OrderServiceImpl,而是注入它的 代理对象

调用 createOrder() 的时候,代理先织入事务逻辑,再调原始方法。


Step 4:事务管理器接手全局

代理本身不负责事务,它只是"调用前的拦截点"。

真正的事务逻辑由 事务管理器(PlatformTransactionManager 接管:

ini 复制代码
TransactionStatus status = transactionManager.getTransaction(def);
try {
    method.invoke(target, args);
    transactionManager.commit(status);
} catch (Throwable ex) {
    transactionManager.rollback(status);
    throw ex;
}

这就是 Spring 在幕后帮你写的"try-catch-commit-rollback"逻辑。


踩坑瞬间:事务不生效的 4 大经典原因

@Transactional 最容易踩的坑,基本都和"代理机制"有关:

  1. 自调用导致绕过代理
    在同一个类内部调用事务方法,不会经过代理,事务不生效。
  2. 方法不是 public
    默认的事务代理只会拦截 public 方法,private / protected 都拦不住。
  3. 异常被 catch 掉了
    Spring 默认只对 RuntimeExceptionError 回滚,如果你捕获了异常或抛的是受检异常,事务也不会回滚。
  4. Bean 没被 Spring 管理
    自己用 new 创建的对象没有代理,自然不会触发事务机制。

面试官杀手锏回答

问:Spring 的 @Transactional 是怎么生效的?

答题模板👇:

  • @Transactional 本质只是元数据标记,不具备事务功能;
  • Spring 在解析 Bean 时,会通过 AOP 创建代理对象
  • 调用事务方法时,会先进入代理逻辑,代理再调用事务管理器开启事务;
  • 方法执行后,根据是否抛出异常决定提交还是回滚;
  • 因为依赖代理,所以 自调用、非 public 方法、异常未抛出 都可能导致事务失效。

一句话总结: @Transactional 的核心在于 AOP 代理 + 事务管理器的协作。


写在最后:注解不是魔法,底层才是本事

很多人第一次用 Spring 事务时,都会掉进"注解即万能"的陷阱里。

但真正理解了代理和 AOP 的角色后,你才会明白:

  • 注解只是声明意图
  • AOP 才是织入逻辑的入口
  • 事务管理器才是落地执行者

所以,会用 @Transactional 是入门,会解释它为什么生效,才是你和别人拉开差距的地方。

下次再被问到"Spring 事务是怎么实现的",别再只说"靠注解",说出"代理、AOP、事务管理器"这三件套,面试官会对你另眼相看。

相关推荐
toobeloong3 小时前
Electron 从低版本升级到高版本 - 开始使用@electron/remote的改造教程
前端·javascript·electron
悠哉摸鱼大王3 小时前
前端获取设备视频流踩坑实录
前端·javascript
铅笔侠_小龙虾3 小时前
深入理解 Vue.js 原理
前端·javascript·vue.js
西西学代码3 小时前
Flutter---showCupertinoDialog
java·前端·flutter
你的眼睛會笑3 小时前
vue3 使用html2canvas实现网页截图并下载功能 以及问题处理
前端·javascript·vue.js
晨非辰3 小时前
【面试高频数据结构(四)】--《从单链到双链的进阶,读懂“双向奔赴”的算法之美与效率权衡》
java·数据结构·c++·人工智能·算法·机器学习·面试
ZTLJQ3 小时前
植物大战僵尸HTML5游戏完整实现教程
前端·游戏·html5