Spring Debugger
2025 年 9 月,JetBrains 发布了 Spring Debugger 插件。短短几个月,下载量突破 30 万。
社区反馈中,有一个问题反复出现:
“能不能调试远程应用?”
2026 年 1 月,这个问题有了答案。
表面上看,这是一次功能补全。但深入了解后,你会发现背后的技术权衡很有意思。
IDEA Spring Debugger 开发团队定了一条铁律:
“我们不想使用任何 debug agents。”
先说说什么是 agent。
在 Java 世界里,agent 是一段附加到 JVM 上的程序。它可以在应用启动时或运行时注入,用来增强或监控应用的行为。比如性能监控工具(APM)、字节码增强框架,都是通过 agent 实现的。
启动 agent 的典型方式是这样:
1 | java -javaagent:/path/to/agent.jar -jar your-app.jar |
对于调试工具来说,agent 可以帮你做很多事:在方法执行前后埋点、拦截特定调用、读取内存数据。听起来很强大,但问题也很明显:
- 启动配置变复杂(要加一堆 JVM 参数,还得指定 agent 路径)
- 维护成本高(agent 版本要和应用版本、JVM 版本匹配,不然可能启动失败)
- 可能干扰调试流程(agent 本身会修改字节码,也会有 Bug)
本地调试还好说。但远程调试就麻烦了。
没有 agent,你就无法在应用启动时”埋点”获取 Spring 上下文。本地调试时,Spring Debugger 会在上下文初始化完成的方法上设个非挂起断点,偷偷读取所有 Bean。
但远程调试时,你连上去的时候应用早就启动完了。那个时机过了。
怎么办?
答案藏在容器的线程模型里
理解这个细节,是理解整个设计的关键。
Tomcat 的做法
Apache Tomcat 很直接。
服务器一启动,connector 就维护一个 worker 线程池。这些线程时刻准备着,像餐厅里的服务员,随时等客人(HTTP 请求)进门。
所以 Spring Debugger 可以在应用启动后,立即”抓”住一个空闲 worker 线程,从这个线程里读取 Spring ApplicationContext。
不用等 HTTP 请求。线程在那里,上下文在那里。
Jetty 和 Undertow 就不一样了
它们的设计更复杂。
Jetty 的模型是:I/O 线程(像门口迎宾)+ Worker 线程(像后厨厨师)。Undertow 类似,I/O 线程处理网络,Worker 线程处理请求。
关键是,worker 线程不是一开始就创建好的,而是有客人进门才叫来厨师。
这就导致一个结果:只有第一个 HTTP 请求到达时,才会有完整的 worker 线程可以访问 Spring 上下文。
所以在 Jetty/Undertow 上,Spring Debugger 连接后不能立即加载上下文。得等第一个请求进来。
说实话,Tomcat 的”线程池常驻”在调试场景反而是优势。虽然占内存,但换来立即可用的调试体验。
它到底能干什么?
理解了设计后,我们看看实际能力。第一次用的时候,会觉得有点”魔法”。 和本地debugger 一样体验,充分结合 Spring 特性,相当于图形化版本的 Arthas。
本地访问任意生产Bean
我之前调试一个应用代码,在 BookmarkService.createBookmark() 上打了日志。
以前的做法:改代码,临时注入,重启,调试完再删掉。然后祈祷自己不会忘记删(结果经常提交到 Git 里)。
Spring Debugger 的做法:直接在表达式框里输入 categoryRepository,插件从 ApplicationContext 找到这个 Bean,你可以直接调用。
1 | categoryRepository.deleteAll() // 临时清空 |
不用改代码,不用重启。就像突然拿到了整个 Spring 容器。

其他一些有意思的能力
直接执行 SQL 结合上下文参数:
1 | entityManager.createQuery("SELECT c FROM Category c").getResultList() |
想临时查数据库?不用写 Repository 方法。
查配置属性:
1 | environment.getProperty("spring.datasource.url") |
想看生产环境的配置?不用翻日志。
手动发布事件:
1 | applicationEventPublisher.publishEvent(new CustomEvent("test")) |
想测试事件监听器?直接在断点处发。
哦对了,还有事务调试。在事务内的代码暂停时,可以看到隔离级别、传播状态、缓存内容、实体状态(MANAGED、DETACHED 之类的)。在 Debug 窗口展开 transaction 节点就行。

以前调试事务问题基本靠猜。现在能直接看运行时状态。

另外,点击 Bean 选 “Show runtime info”,能看到这个 Bean 被注入到哪些类、依赖哪些其他 Bean。接手老项目时特别有用,不用一个个翻代码找依赖。

还有一个细节:在 .properties 文件里,Spring Debugger 会显示属性的实际运行时值。
1 | server.port=8080 # 实际运行时值:8081(被环境变量覆盖) |
如果属性被多个配置源定义,会显示所有位置,可以直接跳转。再也不用纠结”为什么改了配置不生效”。

远程调试怎么配?

配置过程和标准 JVM 远程调试完全一样。用过 JDWP 的话,这部分会觉得”就这?”
Docker Compose 示例
在配置里加 JDWP 参数:
1 | http-server: |
关键是这行:
1 | JAVA_TOOL_OPTIONS=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 |
这是标准 JVM 远程调试选项。参数解释:
server=y:作为调试服务器等待连接suspend=n:启动时不暂停(设置y的话应用会等调试器连接才启动)address=*:5005:监听所有网络接口的 5005 端口
然后在 IDEA 里创建 Remote JVM Debug 配置,指定 IP 和端口(5005)。
重要:记得指定 module classpath。不然连上了也看不到源码,用不了 Spring Debugger 的高级功能。
点 Debug,连接到远程应用。Tomcat 容器的话上下文立即可用。Jetty/Undertow 的话发个 HTTP 请求,上下文就加载了。
之后就能设置断点、评估表达式、查看属性、分析事务,和本地调试一样。唯一区别是代码跑在远程服务器上。
有些限制
功能虽强,但还有些限制:
只支持嵌入式容器。Tomcat、Jetty、Undertow 可以,独立部署的 servlet 容器(把 WAR 包扔到 Tomcat 服务器那种)不行。
要指定 module classpath。Remote JVM Debug 配置里必须指定,不然 IDE 找不到源码。
数据库结构视图不可用。能看到连接,但看不到表结构。
所以
Spring Debugger 的远程调试支持,不是简单的功能堆砌。
- 零配置成本:只需标准 JDWP 端口
- 无维护负担:不用管 agent 版本和兼容性
- 无干扰调试:不改变应用的运行行为
如果你之前没试过远程调试,现在可以试试。
开个端口,点 Debug,就能深入运行中的应用。检查 Bean、查看属性、分析事务。
不需要 agent,不需要 actuator,只需要一个标准的远程调试端口。
就这样。