原文来自于: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
最容易踩的坑,基本都和"代理机制"有关:
- 自调用导致绕过代理
在同一个类内部调用事务方法,不会经过代理,事务不生效。 - 方法不是
public
默认的事务代理只会拦截public
方法,private
/protected
都拦不住。 - 异常被 catch 掉了
Spring 默认只对RuntimeException
和Error
回滚,如果你捕获了异常或抛的是受检异常,事务也不会回滚。 - Bean 没被 Spring 管理
自己用new
创建的对象没有代理,自然不会触发事务机制。
面试官杀手锏回答
问:Spring 的
@Transactional
是怎么生效的?
答题模板👇:
@Transactional
本质只是元数据标记,不具备事务功能;- Spring 在解析 Bean 时,会通过 AOP 创建代理对象;
- 调用事务方法时,会先进入代理逻辑,代理再调用事务管理器开启事务;
- 方法执行后,根据是否抛出异常决定提交还是回滚;
- 因为依赖代理,所以 自调用、非 public 方法、异常未抛出 都可能导致事务失效。
一句话总结: @Transactional
的核心在于 AOP 代理 + 事务管理器的协作。
写在最后:注解不是魔法,底层才是本事
很多人第一次用 Spring 事务时,都会掉进"注解即万能"的陷阱里。
但真正理解了代理和 AOP 的角色后,你才会明白:
- 注解只是声明意图
- AOP 才是织入逻辑的入口
- 事务管理器才是落地执行者
所以,会用 @Transactional
是入门,会解释它为什么生效,才是你和别人拉开差距的地方。
下次再被问到"Spring 事务是怎么实现的",别再只说"靠注解",说出"代理、AOP、事务管理器"这三件套,面试官会对你另眼相看。