Spring 自调用为什么不触发事务代理
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 上下文,并要求代理暴露当前代理。相比之下,拆分服务或注入代理引用更容易维护。
Java FileSystem.getPathMatcher 怎样区分 glob 与 regex
- 上一篇
- Java FileSystem.getPathMatcher 怎样区分 glob 与 regex
- 下一篇
- 暂无
-
- 文章 · java教程 | 34分钟前 | 正则表达式 · java nio regex PathMatcher glob Filesystem getPathMatcher
- Java FileSystem.getPathMatcher 怎样区分 glob 与 regex
- 496浏览 收藏
-
- 文章 · java教程 | 35分钟前 | Java · java 重定向 httpclient redirect
- Java HttpClient 怎样配置重定向策略
- 201浏览 收藏
-
- 文章 · java教程 | 39分钟前 |
- Java ResourceBundle.Control 怎样自定义资源文件的加载顺序
- 418浏览 收藏
-
- 文章 · java教程 | 40分钟前 | java Overflow WatchService 目录监听
- Java WatchService 收到 OVERFLOW 怎么处理
- 246浏览 收藏
-
- 文章 · java教程 | 40分钟前 |
- Java Pattern 怎样读取命名捕获组
- 156浏览 收藏
-
- 文章 · java教程 | 45分钟前 | Stream · java 正则表达式 Stream Matcher MatchResult results
- Java Matcher.results 怎样把匹配项转成 Stream
- 129浏览 收藏
-
- 文章 · java教程 | 46分钟前 | java 排序 comparator nullsFirst nullsLast
- Java Comparator.nullsFirst 怎样让空值排序稳定
- 322浏览 收藏
-
- 文章 · java教程 | 50分钟前 | java synchronized 虚拟线程 pinning 载体线程
- Java 虚拟线程为什么会被锁固定到载体线程
- 323浏览 收藏
-
- 文章 · java教程 | 1小时前 |
- Java RecordComponent 怎样读取记录组件
- 205浏览 收藏
-
- 文章 · java教程 | 17小时前 | Java · IO · java close OutputStream DeflaterOutputStream finish
- Java DeflaterOutputStream.finish 怎样保留底层流继续写
- 142浏览 收藏
-
- 文章 · java教程 | 17小时前 | 命令行工具 · 安全编程 · java Console System.console readPassword 命令行密码 交互终端
- Java System.console 为 null 时怎么处理密码输入
- 339浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 986次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 991次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 1113次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 1083次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 928次使用
-
- Java 时间格式兼容多个输入格式的方法
- 2026-10-18 501浏览
-
- Java try-with-resources 多个资源关闭顺序是什么
- 2026-09-10 501浏览
-
- 矩阵主副对角线快速定位技巧
- 2026-05-31 501浏览
-
- Java多态优化流程代码与行为分发改进
- 2026-05-26 501浏览
-
- JVM 类元数据双亲委派链表深度解析
- 2026-05-21 501浏览

