Spring Boot 4 升级避坑指南:依赖变化、Undertow 弃用、4.0.5 补丁与 Java 25 虚拟线程落地
Spring Boot 4 升级避坑指南:依赖变化、Undertow 弃用、4.0.5 补丁与 Java 25 虚拟线程落地
〇、先说清素材边界
本文在动笔前,可用的研究素材只包含第三方社区文章的标题与链接 ,全部条目的 heat(热度)为 0、published_at(发布时间)为空,且未读取正文内容。这意味着:凡是涉及具体版本号、修复条数、API 名称、配置属性名与性能数字的内容,本文一律按"待核线索"处理,只在有把握的方法层面给出确定性结论,在事实层面明确区分"已核实 / 社区文章宣称 / 本文建议"三种口径。
因此,本文的定位不是版本日志转载,而是一份可核验、可复现、可回滚的迁移手册:告诉你每个坑要查哪份官方文档、用哪条命令验证、在什么条件下回退,而不是把社区标题当成事实转述。凡涉及"官方已弃用 Undertow""4.0.5 修复 9 个 Bug""启动 0.3 秒""单机 30 万并发""1/8 堆内存"等表述,正文均标注其出处与可信度等级,读者在正式发布或立项前应以 spring.io 官方文档、官方 release notes 与自己的实测为准。
一、先定坐标:你的项目到底该不该现在升
1.1 三类项目画像与升级优先级
升级不是一次性工程,而是三笔独立的账:依赖账、容器账、运行时账。不同形态的系统,这三笔账的权重完全不同。
| 项目画像 | 主要风险点 | 建议优先级 | 说明 |
|---|---|---|---|
| 单体 CRUD、流量平稳 | 第三方 starter 兼容、自动配置行为变化 | 中 | 可按部就班,先到 3.5.x 最新补丁,清空 deprecation 再动 4.0.x |
| 微服务(网关 + 业务服务) | BOM 版本对齐、Spring Cloud 组合、链路追踪兼容 | 高(但分批) | 以"一个服务先升级、其余保持旧版"做兼容性验证 |
| WebSocket / 长连接服务 | 容器切换、会话管理、空闲超时、粘性会话 | 最高关注 | 与 Undertow 弃用讨论直接相关,必须先做协议层回归 23 |
1.2 版本坐标表:先查官方支持矩阵,再填自己的表
社区已有文章对 Spring Boot 3.3.x、3.4.x、3.5.x 的演进做了对比 4,也有文章直接讨论 4.0.x 相对 3.5.x 的包依赖变化 1,但这些都属于 UGC(用户生成内容),版本号、支持周期、JDK 范围必须以 spring.io 的系统需求页、各版本 release notes 与官方迁移指南为准。建议在升级立项会上,由架构负责人现场填完下表并留档:
| 你的版本 | Spring Framework 版本 | 官方支持的 JDK 范围 | OSS 支持截止 | 数据来源与访问日期 |
|---|---|---|---|---|
| 3.3.x | 待核(官方 release notes) | 待核 | 待核 | spring.io,YYYY-MM-DD 访问 |
| 3.4.x | 待核 | 待核 | 待核 | 同上 |
| 3.5.x | 待核 | 待核 | 待核 | 同上 |
| 4.0.x | 待核 | 待核 | 待核 | 同上 |
这里有一个容易被标题误导的点:素材中存在"Java 26 + Spring Boot 3.5"的表述 8,但某个组合能否跑通 与是否属于官方支持矩阵是两件事。前者是社区自测,后者是风险承诺。生产选型应以后者为准,并把前者的结果写进"非官方组合风险说明"。
1.3 升级前置检查清单
在任何 4.0.x 改造开始前,先确认以下四项已经完成,否则升级会把旧债和新问题混在一起,导致无法归因:
- 当前版本已停在 3.5.x 最新补丁,而不是直接从 3.3.x 跳到 4.0.x。小版本之间的 deprecation 清理,是降低大版本迁移噪音的前提 45。
- 构建输出中没有未处理的弃用警告 。把
-Xlint:deprecation、--release、编译器警告配置成 CI 可失败项。 - 集成测试覆盖到装配与启动路径。只覆盖 Controller 层单测的项目,自动配置变更往往要到运行期才暴露。
- 依赖树已导出留档,用于升级前后 diff:
bash
# Maven:导出运行时依赖树
mvn dependency:tree -Dverbose -DoutputFile=deps-3.5.txt
# Gradle:导出运行时类路径
./gradlew dependencies --configuration runtimeClasspath > deps-3.5.txt
# 升级后再次导出,做结构化 diff
diff -u deps-3.5.txt deps-4.0.txt | tee deps-diff.txt
二、包依赖与自动配置:沿 3.3.x → 3.5.x 的演进脉络读懂 4.0.x
2.1 演进脉络:3.3.x 到 3.5.x 期间发生了什么
社区对 3.3.x / 3.4.x / 3.5.x 的差异有专门的对比文章 4,对 3.5 的自动配置升级也有解读 567。这些文章可以作为线索清单 ,帮助你形成检查项,但结论必须回到官方各版本 release notes 与参考文档复核。可以确定的方法论是:Spring Boot 的自动配置能力持续向"更细粒度的条件装配、更清晰的模块边界"方向演进,第三方 starter 的可插拔性增强,同时也意味着类路径上少了某个类、多了一个冲突版本时,条件评估结果会比过去更早、更明确地拒绝装配。
实操上不要凭记忆判断,而是把三个版本的变更记录并排读:
- 官方 release notes 中标注 "Deprecation""Removal""Auto-configuration""Starter" 的条目,逐条标注"影响我的项目 / 不影响 / 未知"。
- 官方迁移指南中给出的坐标变更,直接抄进自己的
pom.xml/build.gradle改造单。 - 无法在官方文档中找到依据的说法,统一降级为"社区文章称......,待核"14。
2.2 4.0.x 的依赖坐标变化:按"变更点---影响面---验证命令"三列落地
社区已有文章标题直接指向"spring-boot 4 相比 3.5.x 的包依赖变化"1,这是本次选题的核心线索,但正文内容未获取,因此本文不复述任何具体坐标变更。建议按下表格式自行核对与填写,这是最不容易出错的做法:
| 变更点(来自官方迁移指南) | 影响面 | 触发阶段 | 验证命令 |
|---|---|---|---|
| parent / BOM 引用方式 | 所有模块版本管理 | 构建 | mvn help:effective-pom |
| groupId / artifactId 调整 | 直接依赖该模块的代码 | 编译 | mvn dependency:tree -Dincludes=<groupId> |
| 模块拆分或移出 | 反射、SPI、自动配置 | 启动 / 运行 | 启动加 --debug 看条件评估 |
| 第三方 starter 不兼容 | 集成层 | 启动 | 最小集成测试 + 依赖树 diff |
一个值得借鉴的做法是:把依赖变更按"编译期就炸 / 启动期才炸 / 运行期才炸"分类 。编译期问题可以在 CI 一次性拦截;启动期问题要靠启动测试和 --debug;运行期问题最隐蔽,必须有灰度与可观测性兜底(见第四节)。
2.3 自动配置失效的三种典型症状与定位手法
症状一:Bean 找不到。 表现为 NoSuchBeanDefinitionException 或 UnsatisfiedDependencyException。定位顺序是:先看类路径里条件类是否存在,再看你的 @Bean 定义是否被条件注解挡掉,最后才怀疑装配顺序。
症状二:条件不满足。 表现为某个自动配置类没有生效,但不报错,功能静默消失。用启动时输出条件评估报告:
bash
java -jar app.jar --debug > condition-report.txt
# 关注两类行:
# Positive matches ------ 条件满足、自动配置生效
# Negative matches ------ 条件不满足,说明为什么被排除
# Exclusions ------ 被显式排除的配置
# Unconditional classes ------ 无条件装配的配置
条件评估报告的输出结构在不同大版本间可能有差异,具体开关名与端点名须按目标 4.0.x 版本的官方参考文档核实,不要沿用 3.x 的记忆。Actuator 若启用,也可以结合条件评估相关的端点做运行期查询,端点同样以官方文档为准。
症状三:装配顺序或优先级变化。 表现为同类型多 Bean 时选中的目标变了。用 @Primary、@Qualifier、@Order/@AutoConfigureOrder 显式表达依赖关系,比依赖"隐式顺序"可靠得多;同时把这类改动记录到升级笔记里,因为它们在回滚时同样需要回退。
2.4 第三方 starter 兼容排查
Spring Cloud 与 Spring Boot 存在明确的版本配对关系,社区已有文章讨论 Spring Boot 3.5 与 Spring Cloud 2025.0 的组合 6。升级到 4.0.x 时,务必核对 Spring Cloud 官方的 release train 兼容矩阵,不要只升 Boot 不升 Cloud。建议维护如下对齐表,只填写经官方 BOM 页面核实的版本号,未知项写"待发布/待核":
| 组件 | 3.5.x 使用版本 | 4.0.x 应对版本 | 来源 | 状态 |
|---|---|---|---|---|
| Spring Cloud | 待核 | 待核 | 官方 release train 页 | 待核 |
| MyBatis / MyBatis-Plus | 待核 | 待核 | 项目官方发布说明 | 待核 |
| 数据库驱动 | 待核 | 待核 | 驱动官方支持矩阵 | 待核 |
| 可观测性组件(Micrometer/OTel) | 待核 | 待核 | 官方 BOM | 待核 |
三、嵌入式容器选型:Undertow 说法核实、Tomcat 路径与 WebSocket
3.1 先核实再下结论:"弃用 Undertow"到底是什么措辞
这是本文最需要降噪的地方。社区标题写的是"Spring Boot 4.0 官宣:弃用 Undertow"3,但**"deprecated""不再作为默认""计划移除""已移除"是四个完全不同的状态**,对应的风险等级依次递增:
| 官方措辞 | 含义 | 对迁移的要求 |
|---|---|---|
| Deprecated | 仍可用但不再推荐 | 可延后,但排期跟进 |
| No longer the default | 默认值变化 | 必须显式检查配置是否依赖默认 |
| Planned for removal | 将来版本移除 | 立项迁移 |
| Removed | 已移除 | 立即迁移,否则无法升级 |
本文未获得官方原文,因此不下结论。落地动作是:打开 spring.io 的 Spring Boot 4.0 release notes、官方迁移指南与 GitHub spring-projects/spring-boot 的 release/changelog,找到关于 Undertow 的原句,把上面表格中对应的一行打勾,并把原句与链接贴进升级工单。如果原文只是"不再作为默认"或"计划移除",对外沟通也应使用原措辞,避免标题党式的传播造成误判。
3.2 Undertow → Tomcat 迁移差异清单
无论官方措辞如何,把 Tomcat 作为首选目标容器都是合理的技术储备。迁移时需要逐项核对(每项标注"官方文档出处"或"需自测"):
| 差异项 | 需要做的动作 | 依据类型 |
|---|---|---|
| 依赖坐标 | 移除 Undertow starter,引入 Tomcat starter,排除默认传递依赖 | 官方迁移指南 |
| 配置项前缀 | 全局搜索 server.*、容器专属前缀配置,逐项映射 |
官方配置元数据 |
| 线程/IO 模型 | 压测对比连接数、线程数、吞吐与尾延迟 | 需自测 |
| 优雅停机 | 核对停机超时、在途请求处理、健康检查摘流时机 | 官方文档 + 自测 |
| 压缩与缓冲行为 | 对比响应体压缩、缓冲区大小、大文件下载表现 | 需自测 |
| 错误页与响应头 | 对比 4xx/5xx 行为、Server 头、默认响应头差异 | 需自测 |
这里最容易被忽略的是配置项映射。不同容器的配置前缀和默认值并不一致,把 Undertow 的调优参数原样复制到 Tomcat 上,轻则不生效,重则造成连接数或超时语义变化。
3.3 WebSocket 场景注意事项
WebSocket 是容器切换中最容易出回归的场景,建议按以下清单逐条验证:
- 握手与升级:HTTP Upgrade 流程、握手超时、Origin 校验策略是否一致。
- 会话管理:连接建立/关闭事件、异常断开清理、会话数上限。
- STOMP/SockJS 回退:若使用 SockJS,确认回退传输在新容器下的行为一致。
- 负载均衡:长连接需要连接级负载均衡;若使用 STOMP 会话订阅,注意粘性会话与消息代理的配合。
- 空闲超时:容器层、代理层(Nginx/网关)、应用层的超时是三层独立设置,最长与最短的错配会造成"莫名其妙掉线"。建议统一登记三层超时值,并以心跳机制覆盖空闲窗口。
- 背压与缓冲:大量推送场景下比较两种容器的写缓冲与慢消费者处理策略。
3.4 4.0.5 补丁核对表
社区标题称"Spring Boot 4.0.5 发布:9 个 Bug 修复,WebSocket 用户需关注"2。由于未获取官方 release notes,本文不转述具体修复内容与条数。正确做法是从官方 release notes / milestone 逐条拉取,填入下表:
| issue/PR 编号 | 症状描述 | 影响版本 | 是否需要用户改代码 | 我的项目是否命中 |
|---|---|---|---|---|
| 待核 | 待核 | 待核 | 待核 | 待核 |
判读原则:补丁版本中纯内部修复 通常直接升级即可;行为变更类修复 (尤其是 WebSocket 会话、超时、异常处理)必须在灰度环境做协议层回归;公开 API 变化则要重新走编译与集成测试。
3.5 迁移与回滚方案
容器切换建议采用"双部署对照":
- 新容器实例只接影子流量或低风险流量,与旧容器实例并存。
- 对比关键指标:连接建立成功率、握手耗时、掉线率、P99 延迟、GC 与线程数。
- 保留开关化回退条件:一旦出现连接异常率上升、尾延迟劣化超过预设阈值,切回旧容器。
- 回退不只是改配置,还包含依赖回退与镜像回退,务必在演练中实测回退耗时。
四、Java 25 / JDK 26 虚拟线程:从 Thread-per-request 到可灰度落地
4.1 版本前提澄清
社区素材同时出现"Java 25 虚拟线程"11121314 与"Java 26 + Spring Boot 3.5"8 的表述,容易让人把两个 JDK 版本混为一谈。按长期执行的六个月发布节奏,Java 25 属于长期支持(LTS)线,Java 26 属于常规特性版本;具体发布状态与支持周期必须以 Oracle/OpenJDK 官方路线图核实。生产环境选 LTS 是默认策略,除非有明确的特性需求。
其次,Spring Boot 4.0.x 官方支持的 JDK 范围,必须查 spring.io 的系统需求页。如果某篇社区文章使用的组合(如 Java 26 + 某个 Boot 版本)不在官方支持矩阵内,那么它的结论只能视为社区自测,不能作为生产选型依据。
4.2 Thread-per-request 改造清单
Spring Boot 3.2 起提供了虚拟线程相关的配置能力,社区与既有文档中常见的属性写法是 spring.threads.virtual.enabled;在 4.0.x 上必须以官方 reference 文档复核属性名、默认值与适用范围,本文不把它当作已核实事实。
yaml
# 仅为示意,属性名与默认值须按目标版本官方文档核实
spring:
threads:
virtual:
enabled: true
需要同时明确的边界:
- Servlet / MVC 栈是虚拟线程收益最直接的场景:每个请求一个虚拟线程,阻塞式代码可以保持原有写法。
- WebFlux 响应式栈的模型不同,虚拟线程不会带来同等收益,盲目把响应式项目"改回阻塞式"反而引入大规模重写风险。两者应按团队技术栈与延迟模型分别评估,不做一刀切。
4.3 ExecutorService 与线程池迁移
JDK 21 提供了标准 API Executors.newVirtualThreadPerTaskExecutor()(JDK 21+ 引入,属于标准 API,可以放心写)。典型改造如下:
java
// 改造前:固定大小平台线程池,任务排队,存在拒绝策略与背压
ExecutorService pool = Executors.newFixedThreadPool(64);
try {
for (Task t : tasks) {
pool.submit(() -> handle(t)); // 任务可能排队
}
} finally {
pool.shutdown();
}
// 改造后:每任务一个虚拟线程,不排队;但也不再有并发上限保护
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (Task t : tasks) {
executor.submit(() -> handle(t)); // 每个任务独占一个虚拟线程
}
}
哪些池该换:任务以阻塞等待为主(网络 IO、数据库调用、RPC)、并发度受限于外部资源而非 CPU 的工作池。
哪些池不该换:
- 用于限流与背压的池。固定线程数本身就是一种天然的并发上限;换成每任务一线程后,瞬时洪峰会直接打到下游。要么保留原池,要么在业务层补上信号量/限流器。
- CPU 密集型池。虚拟线程不增加 CPU 算力,CPU 密集任务的池大小应与核心数挂钩,虚拟线程化没有收益。
- 第三方库内部线程池。它们往往有自己的池化策略,不会因为应用开启虚拟线程而自动改变,需要逐个排查。
典型陷阱:
synchronized内执行阻塞 IO 曾造成载体线程 pinning。不同 JDK 版本对此有不同程度的改进,不要凭印象判断,用 JFR 事件在目标 JDK 上实测。ThreadLocal在海量虚拟线程下会被放大,内存占用与初始化开销随之上升;可评估迁移到 ScopedValue(以目标 JDK 文档为准)。Thread.sleep、阻塞 IO 的语义对虚拟线程是友好的,但与信号量、并发限流配合时要重新计算实际并发上限。- 未改造的第三方内部线程池,可能成为新的瓶颈点。
一个用于验证 pinning 的最小示例(自测用途):
java
// 疑似 pinning 场景:synchronized 块内执行阻塞 IO(历史行为,须在目标 JDK 实测)
private final Object lock = new Object();
void callRemote() {
synchronized (lock) {
restTemplate.getForEntity("http://backend/slow", String.class); // 阻塞 IO
}
}
4.4 可观测性补齐
虚拟线程带来的最大运维变化是:线程数量暴增、线程 dump 可读性下降、传统"线程数告警"失效。落地前必须补上:
- JFR 事件采集:关注虚拟线程相关事件与 pinning 相关事件(事件名按目标 JDK 文档核实):
bash
java -XX:StartFlightRecording=filename=vt.jfr,settings=profile -jar app.jar
# 用 JDK 自带工具查看
jfr print --events jdk.VirtualThreadStart,jdk.VirtualThreadPinned vt.jfr
- 指标体系切换:从"线程池活跃数/队列长度"切换到"虚拟线程数量、载体线程数、GC 时间、CPU、上下文切换、下游并发数"。
- Trace 上下文传递:确认链路追踪 SDK 对虚拟线程的上下文传播支持情况(以厂商文档与实测为准),否则 trace 会断裂。
- 超时与限流:没有了线程池队列,就要显式设置连接超时、读超时与并发上限,避免慢下游把整个服务拖垮。
4.5 灰度实践路径
社区有"金融级灰度实践""高并发微服务迁移手记"等文章 111215,但其业务数据未经核实,本文只给可移植的方法论:
开关分层:JVM 级(启动参数)→ 服务级(配置中心)→ 实例级(灰度分组)→ 流量级(按租户/接口路由)。任一层都必须能在分钟级回退。
每阶段通过门槛(示例阈值需按业务自定):
| 阶段 | 观测指标 | 通过门槛 | 回滚触发条件 |
|---|---|---|---|
| 1% 流量 | P99、错误率、GC | 与基线偏差在约定范围内 | 任一指标劣化超过阈值 |
| 10% 流量 | 增加上述指标 + 下游并发 | 下游未触发限流 | 下游 RT/错误率劣化 |
| 50% 流量 | 峰值压测 + 长稳 | 长稳 24h 无异常 | 出现线程/内存异常增长 |
| 全量 | 全部指标 + 成本 | 满足成本目标 | 保留 30 天可回滚窗口 |

回滚耗时目标必须在演练中实测并写进预案,而不是在事故时才估算。
五、冷启动优化三件套:虚拟线程 / GraalVM 原生镜像 / CRaC
5.1 先把问题拆对
这三件事经常被混为一谈,实际上它们解决的是三个不同问题:
| 手段 | 解决什么 | 不解决什么 |
|---|---|---|
| 虚拟线程 | 并发吞吐、线程资源占用 | 冷启动时间、构建时间 |
| GraalVM 原生镜像 | 启动时间、常驻内存(通过构建期 AOT) | 构建时长、调试便利性、生态兼容成本 |
| CRaC | 已启动进程的快速恢复(检查点/恢复) | 首次冷启动、构建成本、外部资源一致性 |
把虚拟线程当成启动优化手段,是最常见的误判 1014;它优化的是并发模型,不是类加载与初始化开销。
5.2 GraalVM 原生镜像
收益来自构建期 AOT 处理与封闭世界假设,代价是构建链路复杂化:反射/资源/代理元数据需要显式声明或由框架在 AOT 阶段生成,构建时长与 CI 成本显著上升,出问题时调试难度高于 JVM 模式。Spring Boot 对 native 的支持能力随版本演进,具体支持范围与限制以目标版本官方文档为准。
适用边界:函数计算、Serverless、短生命周期任务、对启动时间极度敏感的场景。不适用边界:大量使用动态反射/字节码生成/复杂代理的框架组合、构建预算紧张的团队、对热身性能要求高的长驻高吞吐服务(JIT 的稳态吞吐通常更高)。
5.3 CRaC
CRaC 的思路是:让应用完成初始化后打检查点,需要时直接恢复,从而跳过初始化。它对运行环境有明确前提:JDK 发行版支持、足够的进程权限、文件系统与外部连接的一致性、恢复后连接池/网络资源的重建。它和容器/K8s 的配合(例如扩缩容时从检查点拉起)需要额外的镜像与编排设计。
Spring Boot 对 CRaC 的集成现状与维护状态,必须在官方文档与 GitHub 仓库中核实;社区素材中的 CRaC 实测文章 9 只能作为线索,不能作为集成成熟度的依据。若官方集成缺失或维护停滞,应把"自研维护成本"计入决策。
5.4 叠加使用与冲突点
- native + CRaC :是否可行取决于双方的官方支持声明,未获官方明确声明前不要臆测,只能写"未获支持声明,需实测"。
- 虚拟线程 + native:需关注元数据完整性与运行时行为差异,同样以官方文档与实测为准。
- 三者叠加:收益并非线性相加,复杂度却会叠加。除非有明确的 SLA 目标,否则不建议"全都要"。
5.5 选型决策表
| 场景 | 首选 | 次选 | 不建议 |
|---|---|---|---|
| 长驻高吞吐微服务 | 虚拟线程 + 常规 JVM 调优 | GraalVM(若启动预算紧) | CRaC(收益有限、运维复杂) |
| Serverless / 函数计算 | GraalVM native | CRaC(若有支持) | 仅用虚拟线程 |
| 突发流量、快速扩缩容 | CRaC(若集成可用) | GraalVM | --- |
| CPU 密集型计算 | 传统线程池 + JVM | --- | 虚拟线程 |

六、性能宣传口径的复测建议:0.3 秒、30 万并发、1/8 堆内存
6.1 逐条拆口径
社区标题中出现了"启动仅 0.3 秒"10、"单机 30 万并发"12、"百万连接仅需 1/8 堆内存"14、"启动从 3 秒压到 0.8 秒"8 等表述。这些数字来自标题式宣传,测试条件未披露,本文一律不作为结论引用,只给出复测模板。
"启动 0.3 秒"漏了什么 :冷启动还是热启动?是否含 JIT 预热?是否含连接池初始化与懒加载关闭?测量起点是进程创建、端口可接受连接,还是业务就绪探针返回 200?是否为 native 镜像?未标明测量协议的启动数字没有可比性。
"单机 30 万并发"漏了什么 :是 30 万长连接,还是 30 万 QPS,还是 30 万在途请求?每请求负载与响应体多大?SLA 约束是什么?30 万空闲 WebSocket 连接与 30 万活跃请求,是两个完全不同的命题。只有在明确 SLA(P99、错误率)与负载模型下,高并发数字才有意义。
"1/8 堆内存"漏了什么 :对比基线是什么(传统线程栈 + 堆?容器 memory limit?)、测量的是 RSS 还是 heap、并发量是否对齐、虚拟线程的 ThreadLocal 与栈开销是否计入?内存指标必须在同一负载、同一口径下对比。
6.2 基准方法论
工具组合建议固定并标注版本:
- 微基准:JMH(注意避免测量死代码消除、注意预热与多轮采样)。
- 负载:k6 / wrk / Gatling 任选其一,脚本入库、版本固定。
- 剖析:JFR + async-profiler(版本标注)。
环境声明模板(贴进每份报告):
CPU: 型号 × 核数(是否超线程)
内存: 容量 / 容器 memory limit
JDK: 精确版本 + 全部启动 flags
GC: 收集器与关键参数
网络: 压测机与被测机拓扑、带宽
预热: 是否预热、预热时长
采样: 次数、取中位数/P90、方差
冷启动测量协议:连续 N 次(建议 N≥10)冷启动,取中位数与 P90;每次重启进程并清理相关缓存或明确说明文件缓存状态;明确测量起点与终点(推荐以就绪探针首次成功为准)。
k6 场景示例(脚本按业务修改后入库):
javascript
import http from 'k6/http';
import { check, sleep } from 'k6';
export const options = {
scenarios: {
steady: {
executor: 'constant-arrival-rate',
rate: 2000, // 每秒请求数
timeUnit: '1s',
duration: '5m',
preAllocatedVUs: 500,
maxVUs: 5000,
},
},
thresholds: {
http_req_duration: ['p(99)<200'], // SLA:P99 < 200ms
http_req_failed: ['rate<0.001'], // 错误率 < 0.1%
},
};
const payload = JSON.stringify({ id: '000001' });
export default function () {
const res = http.post('http://target/api/query', payload, {
headers: { 'Content-Type': 'application/json' },
});
check(res, { 'status is 200': (r) => r.status === 200 });
}
6.3 报告模板
把宣传口径改写成可复现结论,用下面这张五列表:
| 结论 | 条件 | 数据 | 误差 | 复现命令 |
|---|---|---|---|---|
| 启动时间为 X | JDK 版本、GC、heap、native/JVM、测量协议 | 中位数 / P90 | 方差或区间 | 完整命令行 |
| 并发容量为 Y | 负载模型、payload、SLA 阈值 | QPS / 在途数 | 压测轮次波动 | k6 脚本路径与版本 |
| 内存占用为 Z | RSS/heap 口径、并发量、limit | 采样值 | GC 周期波动 | 采样脚本 |
对外表述也应统一为:"某社区文章宣称 X,条件未披露;本文在以下条件下复测得到 Y",而不是直接引用 X。
七、收尾:一页迁移检查清单与路线图
7.1 三阶段路线图
阶段一:依赖对齐。 升到 3.5.x 最新补丁 → 清空全部 deprecation 警告 → 导出并归档依赖树 → 跑通全量集成测试。
阶段二:4.0.x 升级与容器切换。 按官方迁移指南逐项改坐标 → 用 --debug 排查自动配置 → Undertow → Tomcat(措辞与风险以官方原文为准)→ WebSocket 协议层回归 → 补丁版本核对表逐条过。
阶段三:虚拟线程与启动优化灰度。 版本前提确认 → 代码与线程池改造 → 可观测性补齐 → 分层灰度 → 基准复测 → 全量与回滚演练。
7.2 可打印检查清单
- 已停在 3.5.x 最新补丁,CI 无 deprecation 警告
- 已导出升级前后依赖树并 diff 留档
- 已按官方迁移指南填写"变更点---影响面---验证命令"表
- 已核实 Undertow 官方措辞,并按 deprecated/removed 对应策略处理
- 已完成 Tomcat 配置项映射与容器差异自测
- WebSocket 握手、会话、超时、粘性会话、心跳逐项回归通过
- 补丁版本修复项逐条标注"是否需要用户动作"
- 已确认 JDK 支持矩阵,Java 25/26 属性与 LTS 状态以官方路线图为准
- 虚拟线程配置属性名与默认值已按目标版本官方文档核实
- ExecutorService 迁移已区分"该换"与"不该换"的池
- JFR 事件、指标体系、Trace 上下文传播已验证
- 灰度开关四层齐备,回滚耗时经过演练实测
- 冷启动三件套按场景选型,未把虚拟线程当启动优化
- 性能数字均附环境声明、测量协议与复现命令
7.3 常见误判汇总
- 把 deprecated 当 removed,导致不必要的紧急迁移或错误的工期评估。
- 把虚拟线程当启动优化,投了人力却没解决冷启动问题。
- 把社区基准当官方数据,用未披露条件的数字做架构决策。
- 只升 Boot 不升 Cloud/第三方 starter,在运行期才暴露不兼容。
- 改造了线程池却没补限流,把排队背压换成了下游雪崩。
- 灰度只有开关没有回滚演练,事故时才发现回退路径不通。
参考资料
1 spring-boot 4 相比 3.5.x 的包依赖变化|掘金|https://juejin.cn/post/7579190764766036008(本次采集仅获得标题与链接,未获取正文,具体依赖坐标变更待与官方迁移指南核对)
2 Spring Boot 4.0.5 发布:9 个 Bug 修复,WebSocket 用户需关注|掘金|https://juejin.cn/post/7621405268673871914("9 个修复"及 WebSocket 相关条目未获官方 release notes 核实,发布日期未采集)
3 Spring Boot 4.0 官网:弃用 Undertow:Tomcat 笑麻了|掘金|https://juejin.cn/post/7568755613875322880(标题措辞与官方原文差异待核,弃用程度以 spring.io / GitHub release notes 原句为准)
4 Spring Boot 3.3.x、3.4.x、3.5.x 深度对比与演进分析|CSDN|https://blog.csdn.net/weixin_36755535/article/details/156732041
5 Spring Boot 3.5 新特性解析:自动配置再升级,微服务开发更高效|CSDN|https://blog.csdn.net/qq_35971258/article/details/147079569
6 Spring Boot 3.5 与 Spring Cloud 2025.0 技术更新解析|CSDN|https://blog.csdn.net/ninetennoe/article/details/149282117
7 Spring Boot 3.5.0 正式发布了|CSDN|https://blog.csdn.net/weixin_43932886/article/details/148522203
8 Java 26 + Spring Boot 3.5,微服务启动从 3 秒压到 0.8 秒|CSDN|https://blog.csdn.net/HHX_01/article/details/159648494(社区自测口径,JDK 与 Boot 组合是否在官方支持矩阵内待核)
9 【SpringBoot 3.x 第 64 节】Spring Boot 3.x 玩出花了?CRaC 毫秒级启动听起来像科幻,结果我真试成了!|CSDN|https://blog.csdn.net/weixin_43970743/article/details/156758152
10 Spring Boot 3.5 正式普及!Java 虚拟线程 + GraalVM 原生镜像,启动仅 0.3 秒|CSDN|https://blog.csdn.net/jiangjunshow/article/details/159175645("0.3 秒"测试条件未披露,本文仅作待核线索)
11 Java 25 虚拟线程在金融级系统中的灰度实践(JVM 调优 + 可观测性全链路闭环)|CSDN|https://blog.csdn.net/CodeTrick/article/details/160405516
12 Java 25 虚拟线程落地实践(高并发微服务迁移手记:从 ThreadPerRequest 崩溃到单机 30 万并发稳如磐石)|CSDN|https://blog.csdn.net/CodePulse/article/details/159981366("30 万并发"负载模型与 SLA 条件未披露)
13 JDK25 虚拟线程实战指南|CSDN|https://blog.csdn.net/wDRLiPVfXC/article/details/152707175
14 Java 25 虚拟线程落地指南:从 Thread.sleep() 到百万连接仅需 1/8 堆内存,你还在用 ExecutorService?|CSDN|https://blog.csdn.net/PixelIsle/article/details/160372272("1/8 堆内存"的对比基线与测量口径未披露)
15 Spring Boot 3.3 + Java 25 虚拟线程微服务改造全链路(金融级灰度发布避坑指南)|CSDN|https://blog.csdn.net/CompiTide/article/details/160015367
16 lambda-fusion:基于 Spring Boot 4 / Spring Cloud 2025.1 / JDK 21 的全栈微服务开发框架|Gitee|https://gitee.com/lovesource/lambda-fusion-parent(作为 Spring Boot 4 生态采用情况的旁证,版本组合细节待核)
说明:以上条目均来自本次采集数据,采集记录中 heat 全为 0、published_at 全为空,故本文未使用任何热度排序或发布日期表述;正文涉及的官方版本号、属性名、修复条数、JDK 支持范围与性能数字,均需在发布前以 spring.io 官方文档、官方 release notes 与自测数据最终核验。