一.核心原因AopContext.currentProxy()的局限性AopContext.currentProxy()的作用是获取当前执行线程中正在运行的 Bean 的代理对象。在主线程中请求进来Spring AOP 拦截了方法此时当前线程主线程的上下文中存在代理对象。调用 AopContext.currentProxy() 能拿到代理进而触发事务。在子线程中上下文丢失子线程是全新的线程它没有继承主线程的 Spring AOP 上下文。非代理环境子线程执行的代码通常是一个 Runnable 或 Callable 任务这个任务本身并不是由 Spring 容器管理的 Bean 方法调用链的一部分。结果在子线程中调用 AopContext.currentProxy()通常会抛出 IllegalStateException: Current proxy is not available 异常或者拿到的不是预期的代理对象导致事务无法开启。二. 场景还原黑马点评的异步秒杀在黑马点评项目中为了提升性能我们将耗时的“创建订单”和“扣减库存”操作放到了异步线程中执行。错误写法// 主线程 public Result seckillVoucher(Long voucherId) { // 1. 做一些前置校验... // 2. 开启异步任务 CompletableFuture.runAsync(() - { // 【错误点】试图在子线程中获取代理并调用事务方法 // 此时子线程上下文中没有 AOP 代理这行代码会报错 IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy(); proxy.createVoucherOrder(voucherId); }); return Result.ok(排队中); }后果程序崩溃或者事务不生效如果没报错的话导致数据不一致。正确写法既然子线程拿不到代理那我们就在主线程拿到代理然后把代理对象作为参数传给子线程。变相的“从局部变量中取”。// 主线程 public Result seckillVoucher(Long voucherId) { // 1. 做一些前置校验... // 2. 【关键步骤】在主线程获取代理对象存入局部变量 proxy // 此时主线程上下文中存在代理可以成功获取 IVoucherOrderService proxy (IVoucherOrderService) AopContext.currentProxy(); // 3. 开启异步任务将 proxy 作为参数传递进去 (Lambda 表达式捕获局部变量) CompletableFuture.runAsync(() - { // 4. 在子线程中直接使用传入的 proxy 对象 // 注意这里调用的依然是同一个代理对象事务拦截器会正常工作 proxy.createVoucherOrder(voucherId); }); return Result.ok(排队中); }三. 为什么这样就能生效你可能会疑惑“代理对象不是依赖 ThreadLocal 吗传到子线程还能用吗”这里有一个微妙的机制代理对象本身是单例的Spring Bean包括其代理对象在容器中通常是单例的。proxy变量只是持有这个单例对象的引用。事务拦截器的判断当你在子线程调用proxy.createVoucherOrder()时调用确实发生在了子线程。Spring 的事务拦截器TransactionInterceptor会被触发。拦截器会检查当前线程即子线程是否有事务上下文。关键点虽然AopContext在子线程为空但事务管理器PlatformTransactionManager并不依赖AopContext。只要你是通过代理对象发起调用拦截器就会工作。拦截器发现子线程没有事务于是开启一个新事务默认行为REQUIRED绑定到子线程的ThreadLocal上执行 SQL最后提交。总结区别AopContext.currentProxy()强依赖当前线程的 AOP 上下文栈子线程没有所以不能用。proxy.method()只是普通的 Java 方法调用只要手里有代理对象的引用从主线程传过来的在哪里调用都可以触发拦截逻辑。