Spring 自调用为什么不触发事务代理
来源:17golang原创
时间:2026-11-12 01:02:04 225浏览 收藏
Spring 自调用不触发事务代理,核心原因只有一个:默认的 proxy 模式拦截的是“经过代理对象进入 Bean”的调用,而同一个目标对象内部通过 this 调用方法时,调用链已经在目标对象内部,根本不会再次经过代理。于是,即使被调用的方法写了 @Transactional,事务增强也没有机会执行。
官方地址:https://spring.io/
处理时优先把事务方法拆到独立 Bean;如果暂时不能调整类结构,可以通过代理引用重新进入拦截链;只有明确需要拦截类内部调用时,才评估 AspectJ 织入。
先看清 Spring 事务代理拦截了什么
@Transactional 本身是事务元数据,实际行为由 Spring 的事务基础设施和 AOP 代理提供。外部组件拿到的是代理引用,调用入口大致是“调用方 → 代理 → 目标对象”。代理在把调用转给目标方法前,才有机会创建、加入或提交事务。
下面这张静态说明图把两条路径放在一起:蓝色路径从外部调用进入代理,红色路径从目标对象内部直接调用,后者不会回到代理。

为什么 this 调用会绕过 @Transactional
假设一个服务同时包含入口方法和事务方法:
@Service
public class OrderService {
public void placeOrder() {
// 这是目标对象内部调用,不会重新经过 Spring 代理
this.saveOrder();
}
@Transactional
public void saveOrder() {
// 这里的事务增强只有在调用经过代理时才会执行
orderRepository.save(new Order());
}
}
如果控制器或其他 Bean 调用的是代理上的 placeOrder(),placeOrder() 进入代理这一层可以被拦截;但方法体里的 this.saveOrder() 是 Java 对当前对象的直接调用。它没有一次新的代理入口,所以 saveOrder() 上的事务属性不会被代理读取。
把 this.saveOrder() 改成显式的 saveOrder() 也不会改变结果。省略 this 只是语法变化,调用仍然发生在同一个目标对象内部。
先排除几个容易混淆的前提
确认自调用问题前,先看调用对象和 Bean 生命周期,否则可能把多个问题混在一起:
- 类是否由 Spring 容器创建。用
new OrderService()得到的对象没有 Spring 代理。 - 是否启用了事务管理。只有启用注解驱动事务,
@Transactional才会被运行时基础设施处理。 - 调用方拿到的是 Bean 引用还是目标对象本身。只有经过代理的外部入口才有机会进入事务拦截链。
- 接口代理下,事务方法是否是代理接口可见的公开方法;类代理和接口代理对可拦截方法的边界并不完全相同。
- 是否在对象初始化阶段调用。代理尚未完整初始化时,不应把初始化代码当作正常事务入口。
这些前提都满足后,如果问题只发生在同类内部调用,基本就可以按“自调用绕过代理”处理。
最推荐:把事务方法拆到独立 Bean
拆分服务的思路最清楚:外层服务负责流程编排,内层服务负责事务边界。两个 Bean 之间的调用是外部调用,会经过内层 Bean 的代理。
@Service
public class OrderWriteService {
@Transactional
public void saveOrder() {
// 事务边界放在独立 Bean 的公开入口上
orderRepository.save(new Order());
}
}
@Service
public class OrderService {
private final OrderWriteService orderWriteService;
public OrderService(OrderWriteService orderWriteService) {
// 注入的是容器管理的 Bean 引用,调用时会经过代理
this.orderWriteService = orderWriteService;
}
public void placeOrder() {
// 跨 Bean 调用,事务代理可以拦截 saveOrder
orderWriteService.saveOrder();
}
}
这种方式的好处是依赖方向和事务职责都可见,也不会让业务类依赖 Spring AOP 的内部 API。对于长期维护的代码,通常应优先选择它。
不能拆分时:通过代理引用重新进入拦截链
如果类结构暂时不能调整,可以注入自己的代理引用,再通过这个引用调用事务方法。这里的关键不是“调用自己”,而是“调用代理对象代表的自己”。
@Service
public class OrderService {
private final OrderService self;
public OrderService(OrderService self) {
// 容器注入的引用应当是代理,而不是手动 new 出来的目标对象
this.self = self;
}
public void placeOrder() {
// 通过 self 进入代理,事务拦截才有机会执行
self.saveOrder();
}
@Transactional
public void saveOrder() {
// 事务方法保持为公开入口,便于代理拦截
orderRepository.save(new Order());
}
}
自注入会增加类的间接性,也可能让循环依赖和代理类型变得更难理解,因此它更适合作为过渡方案。不要用 new 创建第二个实例来“模拟代理”,那样得到的仍然是没有容器增强的普通对象。
特殊需求:评估 AspectJ 织入
Spring 的 aspectj 模式不是通过代理拦截调用,而是把事务切面织入目标类的字节码,因此可以覆盖类内部调用。它需要相应的 AspectJ 依赖,以及编译期或加载期织入配置,部署和排障成本都高于默认代理模式。
@Configuration
@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)
public class TransactionConfig {
// 使用 AspectJ 模式,让事务切面织入目标类
// 生产环境还需要按项目方式配置 spring-aspects 与织入过程
}
这里的选择不是“把 proxy 改成 aspectj 就万事大吉”。应先确认确实需要类内部调用也具备事务语义,再补齐织入依赖、构建配置和运行环境。若只是一个服务内部的流程调用,拆分 Bean 往往更容易理解。

用一张表选择处理方式
| 方式 | 调用是否经过代理 | 适合场景 | 主要代价 |
|---|---|---|---|
| 拆分服务 | 是,跨 Bean 调用 | 新代码或允许调整职责 | 需要重新划分类和依赖 |
| 代理引用 | 是,通过代理再次进入 | 暂时不能拆分的存量类 | 间接性增加,依赖代理语义 |
| AspectJ 织入 | 不依赖代理入口 | 必须覆盖内部调用的明确场景 | 构建、部署和排障复杂度更高 |
排查时按这个顺序判断
- 确认调用对象来自 Spring 容器,而不是手动
new。 - 从调用点向前追踪:它是其他 Bean 调用,还是同类的
this调用。 - 如果是同类调用,优先把事务方法移到独立 Bean。
- 如果必须保留原类,再检查注入的自引用确实指向代理。
- 只有确实需要内部调用也被增强时,才评估 AspectJ 的织入配置。
一句话总结:@Transactional 标注的是方法语义,但默认 proxy 模式只在调用穿过代理时应用这份语义;同类内部调用不穿过代理,所以不会因为方法上有注解就自动开启事务。
常见问题
把 this.method() 改成 method() 能解决吗?
不能。两者都是当前目标对象内部调用,仍然绕过 Spring 代理。
改成 CGLIB 代理后,自调用会生效吗?
通常不会。CGLIB 改变的是代理实现方式,不改变自调用绕过代理的调用路径。
可以直接使用 AopContext.currentProxy() 吗?
可以作为最后手段,但它会让业务类绑定 Spring AOP 上下文,并要求代理暴露当前代理。相比之下,拆分服务或注入代理引用更容易维护。
-
501 收藏
-
文章 · java教程 | 2个月前 | Java · 异常处理 · 资源管理 · java try-with-resources AutoCloseable close suppressed exception501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
496 收藏
-
201 收藏
-
418 收藏
-
246 收藏
-
156 收藏
-
129 收藏
-
322 收藏
-
323 收藏
-
419 收藏
-
205 收藏
-
142 收藏
-
339 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习