public Result seckillVoucher(Long voucherId) {
synchronized (userID.toString().intern()) {
//获取代理对象
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
}
}
@Transactional
public Result createVoucherOrder(Long voucherId){
Long userID= UserHolder.getUser().getId();
}
1. 根本原因 (Cause)
原因概括:发生了 Spring AOP 的“自调用” (Self-Invocation) 问题。
在代码中,seckillVoucher 方法内部直接调用了 createVoucherOrder 方法。
虽然 createVoucherOrder 上加了 @Transactional 注解,但因为是类内部调用(等同于 this.createVoucherOrder(...)),绕过了 Spring 的代理对象,导致事务切面逻辑无法执行。
要理解这个根本原因首先得理解Spring的动态代理
步骤 1:启动时扫描 Bean
Spring 启动时:
- 扫描所有带
@Service、@Component的类 - 创建这些类的真实对象(target)
OrderService realService = new OrderService(); // 真实对象
步骤 2:检查是否需要增强
Spring 检查 realService 的方法上是否有:
@Transactional@Cacheable@Async- 自定义 AOP 切面
👉 如果有,不直接把真实对象放进容器,而是:
步骤 3:创建代理对象(关键!)
Spring 根据情况选择代理方式:
| 条件 | 代理方式 |
|---|---|
| 类实现了接口 | JDK 动态代理(默认) |
| 类没有实现接口 | CGLIB 代理 |
三、JDK 动态代理在 Spring 中的样子(简化版)
假设你的 OrderService 实现了接口:
public interface IOrderService {
void createOrder();
}
@Service
public class OrderServiceImpl implements IOrderService {
@Transactional
public void createOrder() { ... }
}
Spring 内部会生成类似这样的代理(伪代码):
// Spring 动态生成的代理类(运行时创建,你看不到源码)
public class OrderServiceImplProxy implements IOrderService {
private OrderServiceImpl target; // 真实对象
private TransactionManager txManager; // Spring 注入的事务管理器
public void createOrder() {
// 【前置】检查 @Transactional,开启事务
if (method.hasAnnotation(Transactional.class)) {
txManager.begin();
}
try {
// 调用真实对象的方法
target.createOrder();
// 【后置】提交事务
txManager.commit();
} catch (Exception e) {
// 【异常】回滚事务
txManager.rollback();
throw e;
}
}
}
所以只有调用代理对象的createVoucherOrder才能执行到有事务的createVoucherOrder。
2. 底层原理 (Principle)
Spring 的事务管理是基于 AOP (面向切面编程) 实现的,而 AOP 的核心是 动态代理 (Dynamic Proxy)。
正常调用流程(事务生效):
当 Controller 或其他类调用 Service 时,Spring 容器注入的不是 VoucherOrderServiceImpl 原生对象,而是一个 代理对象 (Proxy)。
-
外部调用 -> 代理对象 (Proxy)
-
代理对象检测到 @Transactional -> 开启事务 (Start Transaction)
-
代理对象调用 -> 目标对象 (Target) 的业务方法
-
方法执行完毕 -> 代理对象 提交/回滚事务
代码调用流程(事务失效):
-
外部调用 seckillVoucher -> 代理对象(该方法无注解,不开启事务)。
-
代理对象调用 -> 目标对象 的 seckillVoucher 方法。
-
关键点:在 seckillVoucher 内部执行 createVoucherOrder()。此时使用的是 this 指针。
-
this 代表的是 目标对象本身,而不是代理对象。
-
直接执行普通方法,没有任何 AOP 代码包裹,所以事务完全不存在。
3.解决方案
IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();
return proxy.createVoucherOrder(voucherId);
获取当前的代理对象,通过代理对象调用。
1035

被折叠的 条评论
为什么被折叠?



