☰
【码动四季·秋】把 Spring Boot 的 Tomcat 换成 Undertow:一次容器替换的三处暗坑
2026/10/30 4:39:31 网站建设 项目流程

本文为 AtomGit 码动四季·开源同行征稿活动参与文章

摘要:Spring Boot 默认内嵌 Tomcat,换成 Undertow 只需"排除 + 引入"两步 pom 操作,但"能跑"和"跑得干净"之间隔着三处暗坑:传递依赖把 Tomcat 悄悄拉回 classpath、Tomcat 表达式语言的组件冲突、server.tomcat.*配置静默失效。本文基于 ArticlePilot 实仓的容器化提交(2026-09-17)逐坑拆解,给出"依赖树终检 + 容器红线写进 AGENTS.md + 配置逐项翻译"三道防线——替换影响面被锁定在 pom 与配置层,业务代码零改动,回退只需还原两处依赖。

文章目录

    • 一、替换的两步标准操作
    • 二、三个暗坑与解法
      • 1:传递依赖把 Tomcat 拉回来,双容器共存的假象
      • 2:Tomcat 表达式语言的传递组件冲突
      • 3:按 Tomcat 行为写的配置悄悄失效
    • 三、验证与回退
    • 四、总结

Spring Boot 默认内嵌 Tomcat,多数项目够用。但 ArticlePilot 是容器化部署的单机管理台,内存 footprint 和连接处理的轻量性比"默认"更值钱,于是在 9 月 17 日的容器化改造里把 Web 容器换成了 Undertow。替换本身只要两步 pom 操作,真正花时间的是随后冒出来的三处暗坑——都与"某条传递依赖悄悄把 Tomcat 拉回来"或"Tomcat 专属行为被默认依赖"有关。这篇把这些坑一次讲完,照着做可以少走弯路。

先交代证据:这是当天容器化改造的原始提交,后面所有的坑都发生在这个提交的前后。

一、替换的两步标准操作

替换的核心思路:排除 starter 里的 Tomcat,再引入 Undertow starter。在spring-boot-starter-web上做排除:

<!-- pom.xml:排除默认 Tomcat,引入 Undertow --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-undertow</artifactId></dependency>

这一步做完启动就是 Undertow 了。但"能跑"和"跑得干净"之间,隔着下面三个坑。

exclude

是

否,仍是 Tomcat

starter-web

移除 starter-tomcat

引入 starter-undertow

启动容器是
Undertow 吗?

第 1 步完成

传递依赖把
Tomcat 拉回来了

二、三个暗坑与解法

1:传递依赖把 Tomcat 拉回来,双容器共存的假象

现象:排除后启动日志确实打了 Undertow,但运行行为怪异——某次引入一个第三方 starter 后,Tomcat 的类又出现在 classpath 里,端口偶尔被双容器抢占。

根因:其他 starter 或业务依赖可能传递引用tomcat-embed-*,排除只对starter-web这一条路径生效,防不住别的路径。

解决:替换完成后用依赖树做一次终检,确认tomcat-embed只允许出现在websocket等明确豁免的路径里,其余一律在对应依赖上补 exclusion:

# 依赖树终检:确认没有旁路把 Tomcat 拉回来mvn dependency:tree-Dincludes=org.apache.tomcat.embed

效果:终检跑完后依赖树里org.apache.tomcat.embed零意外命中,双容器抢占端口的问题此后未再复现。

感受:排除是"点防御",依赖树是"面验收"——单点排除永远防不住全图的传递路径,终检这一步不能省。

教训:容器替换的验收标准是"依赖树里没有多余容器",不是"启动日志换了名字"。

2:Tomcat 表达式语言的传递组件冲突

现象:启动期报 EL 相关的类冲突——项目里某个依赖携带了 Tomcat 的表达式语言实现,与 Undertow 环境下的 JSP/EL 期望不一致。

根因:个别老组件默认按 Tomcat 环境打包,把tomcat-embed-el当成必备依赖传递进来。

解决:在该依赖上显式排除 Tomcat 表达式语言,EL 统一走标准实现;这条约束后来写进了 AGENTS.md 的安全红线:“后端 Web 容器是 Undertow,不要引入依赖 Tomcat 的传递组件”。pom 里的实际排除块长这样——starter-tomcat和tomcat-embed-el是两处独立的排除,缺一不可:

效果:双排除落地后 EL 冲突消失,容器红线写进 AGENTS.md 至今,同类问题零复发——包括 AI 编程助手生成的新依赖引入代码也会遵守这条约定。

感受:一次排查的成本换一条长期生效的红线,是这次替换里性价比最高的动作。

教训:给仓库写一条"容器红线",比每次事后排查传递依赖便宜得多——AI 编程助手和后来的协作者都会读这条约定。

3:按 Tomcat 行为写的配置悄悄失效

现象:替换后个别连接参数不生效。逐项核对才发现,server.tomcat.*前缀的配置项在 Undertow 下是死配置,Undertow 有自己的一套server.undertow.*。

根因:Spring Boot 的配置前缀与容器绑定,换容器等于换配置命名空间,旧配置不报错、只是静默不生效。

解决:按server.undertow.*重写容器配置(工作线程、IO 线程、请求超时、访问日志),并对替换做一次显式验证——启动日志确认容器名 + 压测观察参数是否生效:

# server.undertow.* 替代 server.tomcat.*(Boot 3.2.x 实际键名) server.undertow.threads.io=8 server.undertow.threads.worker=64 server.undertow.no-request-timeout=30s server.undertow.accesslog.enabled=true

常用配置项的迁移对照,替换前逐项过一遍:

用途Tomcat 配置项Undertow 配置项
IO 线程数server.tomcat.threads.min-spare(近似)server.undertow.threads.io
工作线程数server.tomcat.threads.maxserver.undertow.threads.worker
请求超时server.tomcat.connection-timeoutserver.undertow.no-request-timeout
访问日志server.tomcat.accesslog.enabledserver.undertow.accesslog.enabled

效果:配置逐项翻译后,线程与超时参数在压测下按预期生效,没有"配置了但没反应"的残留项。

感受:这张对照表是整个替换里最费时间也最值得的部分——静默失效比报错可怕得多,报错会逼你修,静默失效只在生产上等你。

教训:换容器不是依赖替换,是配置命名空间的迁移,server.tomcat.*要逐项翻译而不是留着当摆设。

三、验证与回退

三个坑对应三道防线,收拢成一张图,替换前照着设防:

验收动作按固定顺序走一遍,任何一步不达标就退回上一步排查:

替换的验证三件套:mvn dependency:tree终检依赖路径、启动日志确认容器、冒烟测试覆盖核心接口。回退方式也很简单——pom 里恢复 starter-tomcat 依赖并删掉 Undertow starter 即可,数据层与业务代码不感知容器差异,这也是容器层替换值得做的原因:影响面被 Spring Boot 的抽象锁在很小的范围里。

四、总结

Tomcat 到 Undertow 的替换,动手只要五分钟,做干净要半天。三处暗坑的共同规律是:替换的边界不是你改的那个依赖,而是整个依赖图里所有隐式依赖 Tomcat 的路径。把"依赖树终检 + 容器红线写进 AGENTS.md + 配置前缀逐项翻译"三件事做完,这次替换才真正结束。

真实性声明

本文操作与坑例来自 ArticlePilot 仓库 2026-09-17 提交(feat(infra): 容器 Undertow 化)与 pom.xml 实际配置,可对照复核;环境基准为 Spring Boot 3.2.5 / Java 17。

开源仓库地址

  • ArticlePilot(pom.xml 排除配置与 AGENTS.md 容器红线可直接对照):https://atomgit.com/dickeryang/articlepilot

参考资源

  • Undertow 官方文档
  • Spring Boot 官方文档:Web Server 配置

专栏导航

  • 上一篇:ArticlePilot 从 0 到开源的迭代复盘
  • 下一篇:稿件并发编辑的乐观锁保护

如果本文对你有帮助,欢迎点赞、收藏、转发。有任何问题或建议,请在评论区留言交流。行文仓促,定有不足之处,欢迎各位朋友在评论区批评指正,不胜感激。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询