摘要今天我在 Spring 开发中遇到了一个场景明明给方法加了Transactional注解但在同一个类中通过this调用该方法时事务却神奇地消失了本文将带你深入 Spring AOP 底层来解释这一问题一.问题现象在 Spring 开发中当一个 Service 类中的非事务方法Method A直接调用同类中标注了Transactional的事务方法Method B时事务注解往往不会生效。Service public class UserService { Autowired private UserMapper userMapper; // 外部入口方法无事务注解 public void createUser() { System.out.println(Step 1: 执行前置逻辑); // ❌ 问题点内部自调用 this.saveUser(); System.out.println(Step 3: 执行后置逻辑); } // 目标事务方法 Transactional public void saveUser() { System.out.println(Step 2: 保存用户数据); userMapper.insert(new User()); // 模拟异常 throw new RuntimeException(DB Error); } }预期行为saveUser抛出异常数据库操作回滚。实际行为异常被抛出但数据库中的数据已被提交事务未生效。这是为什么二.核心原理AOP动态代理机制Spring 的声明式事务管理基于AOP面向切面编程实现其底层依赖动态代理Dynamic Proxy技术。2.1 代理对象的创建当 Spring 容器启动并扫描到标注了Transactional的 Bean如UserService时Spring 不会直接将原始类Target Object注册到容器中而是会创建一个代理对象Proxy Object。原始对象Target包含真实的业务逻辑代码。代理对象Proxy包裹在原始对象外层包含了事务拦截器Transaction Interceptor。容器引用当我们使用Autowired注入UserService时实际注入的是代理对象。2.2 正常调用链路外部调用当外部类调用userService.createUser()时调用链路如下Client调用Proxy.createUser()。Proxy中的拦截器链被触发。由于createUser无事务注解拦截器直接放行。Proxy调用Target.createUser()原始对象方法。在Target.createUser()内部执行this.saveUser()。关键点此时的this指向的是Target原始对象而非Proxy。因此调用直接进入Target.saveUser()完全绕过了 Proxy 中的事务拦截器。Transactional注解定义的“开启事务”、“异常回滚”等逻辑从未被执行。结论Transactional生效的前提是方法调用必须经过代理对象。同类内部的this调用属于对象内部的方法栈帧切换不经过代理层导致 AOP 切面无法织入。三.源码级分析Spring AOP 的核心逻辑位于org.springframework.aop.framework.ReflectiveMethodInvocation。当调用经过代理时执行流程为JdkDynamicAopProxy.invoke()(或 CGLIB 代理) 被调用。构建InterceptorChain拦截器链其中包含TransactionInterceptor。MethodInvocation.proceed()依次执行拦截器。TransactionInterceptor执行invokeWithinTransaction()在此方法中获取PlatformTransactionManager。调用txManager.getTransaction()开启事务。执行目标方法invocation.proceedWithInvocation()。根据执行结果 commit 或 rollback。当发生自调用Self-Invocation时JVM 直接在堆栈中执行Target.methodA-Target.methodB。这是一个纯粹的 Java 内部方法调用没有任何字节码增强或代理拦截介入因此TransactionInterceptor根本没有机会运行。四. 解决方案要解决此问题必须强制让方法调用重新经过代理对象。解决办法暴露代理对象AopContext利用 Spring 提供的AopContext获取当前线程绑定的代理对象。第一步开启配置在配置类或启动类上启用exposeProxyConfiguration EnableAspectJAutoProxy(exposeProxy true) public class AppConfig { }第二步获取代理调用Service public class UserService { public void createUser() { System.out.println(Step 1: 执行前置逻辑); // 获取当前代理对象 UserService proxy (UserService) AopContext.currentProxy(); // 通过代理对象调用 proxy.saveUser(); } Transactional public void saveUser() { // ... } }原理AopContext将当前的 Proxy 对象绑定到ThreadLocal中允许在目标方法内部获取它。缺点代码耦合了 Spring 框架 API (AopContext)且需要修改全局配置侵入性较强。五. 进阶思考事务传播行为即使解决了失效问题还需注意事务传播行为Propagation Behavior的影响。假设createUser也被加上了TransactionalTransactional // 默认 propagation REQUIRED public void createUser() { self.saveUser(); // saveUser 也是 Transactional (REQUIRED) }默认情况 (REQUIRED)saveUser会加入到createUser已有的事务中。两者共用同一个数据库连接和事务上下文。此时若saveUser抛出异常整个createUser的事务都会回滚。独立事务 (REQUIRES_NEW)如果希望saveUser拥有独立的事务即它的提交/回滚不影响外层或者外层失败不影响它已提交的数据需修改注解Transactional(propagation Propagation.REQUIRES_NEW) public void saveUser() { ... }此时代理对象会在调用saveUser时挂起Suspend外层事务创建一个全新的物理事务。六.总结核心结论Spring 的事务管理依赖于代理模式。任何绕过代理对象的直接方法调用尤其是this调用都会导致 AOP 增强逻辑包括事务、日志、安全校验等失效。在设计业务逻辑时应尽量避免同类内的自调用或通过上述方案强制走代理链路。