前段时间我把一个内部项目从 Spring Boot 3.2 升到了 3.4整体耗时比预期短但中间踩了几个不翻官方文档根本想不到的坑。这篇文章就基于这次的升级经历把 Spring Boot 3.4 的“王炸级更新”拆开揉碎讲清楚哪些新特性是真香哪些升级前必须提前确认以及实际项目中怎么落地才稳妥。如果你正准备从 3.2 或 3.3 升到 3.4或者想在下一个新项目里直接用 3.4这篇文章值得你花十分钟看完。Spring Boot 3.4 这次的定位很明确它不是一次激进的重构而是一次“整理书包式”的大版本更新。底层换成了 Spring Framework 6.2默认依赖全面刷新同时对日志、可观测性、HTTP 客户端和数据库迁移这些日常开发天天碰的地方做了不少体验优化。我个人的判断是3.4 属于那种“可以放心升级、但必须先做功课”的版本。下面按主题逐个聊。1. 这次升级最值得关注的几个变化1.1 版本基线JDK 17 与 Spring Framework 6.2先看地基。Spring Boot 3.4 建立在 Spring Framework 6.2 之上要求 JDK 17 起跳官方测试覆盖到 JDK 23。换句话说如果你的项目还在 JDK 8 或 JDK 11那 3.4 直接跟你无缘必须先解决 JDK 升级的问题。不过放在 2025 年这个时间点JDK 17 已经算“新项目的最低共识”了。很多团队早就在生产环境跑了几年 JDK 17虚拟线程、record、switch 模式匹配这些特性也让代码清爽了不少。Spring Boot 3.4 把 JDK 17 作为最低基线其实是把“主流化”这几个字写在了脸上。从 3.2 开始就能用虚拟线程3.4 继续沿用了这套能力只需要一行配置spring.threads.virtual.enabledtrue注意这个开关对 Tomcat 处理请求、以及普通线程池的创建都会生效。启用之后原先那些“为了并发写一堆异步逻辑”的代码很多可以直接改成同步写法由虚拟线程承接。建议先在非核心链路上试跑再逐步放大范围。Spring Framework 6.2 本身也值得单独说。它对Async、Scheduled的底层调度做了不少完善对响应式编程的上下文传播也做了增强。这些不是能直接“看到”的功能点但会减少很多偶发性的边界问题。1.2 依赖清单的集体刷新每一次 Spring Boot 大版本发布最直观的变化其实是依赖版本集体升级。3.4 带来的关键依赖版本大致如下组件3.3 时代典型版本3.4 基线版本影响点Spring Framework6.1.x6.2.x核心 API 与行为变化Tomcat10.1.x10.1.3xWeb 容器行为细节Jetty12.0.x12.0.x 新补丁若用 Jetty 需重点回归Jackson2.17.x2.18.xJSON 序列化细节Micrometer1.13.x1.14.xMetrics 与 Tracing APIMaven 插件3.3.x3.4.x打包与镜像构建行为这个表格看起来只是“版本号变大了”但实际影响不在版本号本身而在于行为细节的调整。比如 Jackson 升级后某些 Java 时间类型的序列化格式可能有细微变化Tomcat 对请求体大小的默认处理逻辑也发生过调整。这些都属于“本地跑一次没感觉上线压测才暴露”的问题。所以我的建议是升级依赖不等于改个版本号就完事必须把自动化测试覆盖率补上尤其是接口返回 JSON 的快照测试和接口超时类的集成测试。1.3 哪些项目适合立刻升哪些可以再等等说实话不是所有项目都适合第一时间升级。我大致分了三类新项目直接上 3.4没有任何历史包袱享受最新特性。老项目但测试完善可以升但按“先本地、再测试环境、最后灰度”的节奏走。老项目且测试严重缺失先别急着升把核心接口的自动化测试补齐再考虑动版本。否则你根本分不清升级引发的问题还是存量问题。另外如果你用了大量第三方组件尤其是一些社区维护的 starter一定要先确认它们是否发布了适配 3.4 的版本。这块我后面专门用一节讲。2. 结构化日志从“能看”到“能查”的一步到位2.1 普通日志的痛点先问一个问题你的日志现在还靠人肉 grep 吗大多数项目的日志现状是用 logback 输出多行文本排查问题时顺着 traceId 或关键字去文件里搜。问题在于日志量一上来文本格式的检索效率低、字段没法精确过滤、告警规则也很难写。比如你想查“下单接口所有耗时超过 3 秒的请求”文本日志只能靠正则硬解析字段一换就崩。用 ELK 那套方案的话每次都要写 grok 解析规则维护成本非常高。结构化日志的思路完全不同每条日志本身就是一行 JSON 或 ECSElastic Common Schema格式的文本时间、级别、logger、消息、异常、自定义字段都是结构化的。采集端拿到手就能直接索引查询和告警都变得很自然。Spring Boot 3.4 这次把结构化日志做成了内置能力这是我认为 3.4 最实用的一项更新。2.2 Spring Boot 3.4 内置的结构化日志怎么用以前想在 Spring Boot 项目里输出 JSON 日志主流方案是引入logstash-logback-encoder之类的第三方库再写一长串 logback 配置。3.4 直接内建了支持只需要在配置文件里声明格式logging.structured.format.consoleecs logging.structured.format.filejson这里有两组维度console对应控制台输出file对应文件输出可以分别设置。值支持ecs、json、logstash。ecs是 Elastic 的标准格式适合采集到 Elasticsearchjson是通用的 JSON 结构适合其他日志平台logstash兼容旧版 Logstash 用户的习惯。如果只写了console那么文件日志保持原来的文本格式如果两个都想改就都写上。对于代码里已有的logger.info(订单创建成功订单号 {}, orderId);这种写法不需要改代码。结构化日志会自动把消息放到message字段里。想要额外塞业务字段用 MDC 就行MDC.put(orderId, orderId); MDC.put(userId, userId); logger.info(订单创建成功); MDC.remove(orderId); MDC.remove(userId);这样日志平台里就能按orderId做精确检索这在文本日志时代是做不了的。2.3 真实项目落地时的细节我在切换过程中遇到三个值得提醒的点。第一异常堆栈字段的表现形式。默认情况下异常信息会放在exception或error字段里具体字段名取决于你选的格式。如果你依赖的是“在日志详情里展开堆栈文本”的排查习惯需要适配一下日志平台侧的展示配置。第二日志量会明显变大。JSON 格式带字段名同样一条日志的体积通常比纯文本多 30% 到 50%。如果你用了日志采样建议尽早把采样规则配置好别等服务流量跑起来再调。第三不要同时启用 logstash-logback-encoder 和内置结构化配置。我已经见过不止一次配置文件里同时出现两种 JSON encoder 导致重复输出的情况。3.4 内置方案已经够用老项目里如果引入了第三方依赖升级时最好顺手清掉。3. REST 客户端全家桶增强同步、异步、响应式的调用体验统一3.1 RestClient 从“试用”走向“默认推荐”Spring 生态的 HTTP 调用客户端演进路线其实挺清晰的一开始是RestTemplate后来响应式时代来了个WebClient再后来 Spring 6.1 推出了RestClient号称结合了两者优点。到了 3.4RestClient的自动配置和 API 完整性已经成熟到可以作为同步调用的首选。RestClient解决的核心问题是RestTemplate的 API 老旧、不好链式调用WebClient虽然 API 现代但默认是响应式的同步场景用起来有点别扭。RestClient用一套和WebClient相似的链式 API但默认同步阻塞心智模型更简单。你可以在 3.4 项目里直接注入Autowired private RestClient.Builder restClientBuilder; public OrderDTO getOrder(Long id) { return restClientBuilder.build() .get() .uri(/orders/{id}, id) .accept(MediaType.APPLICATION_JSON) .retrieve() .body(OrderDTO.class); }如果你不想每次都基于 builder 构建也可以直接定义一个全局RestClientBean。Spring Boot 3.4 会自动帮你配置好基础 URL、默认 Header、超时等参数。3.2 和 RestTemplate、WebClient 怎么选这里我给一张选择参考表场景推荐客户端理由传统 MVC 项目、同步 API 调用RestClientAPI 简洁同步心智异常处理清晰需要高并发且线程资源紧张RestClient 虚拟线程写同步代码但由虚拟线程承载WebFlux 响应式链路WebClient天然响应式避免阻塞事件循环旧项目维护、不想改代码RestTemplate兼容稳定但不建议新代码继续用有一个常见误区以为WebClient就一定比RestClient性能好。其实在同步场景下RestClient的性能和RestTemplate处于同一水平真正带来吞吐提升的是虚拟线程。我自己实测下来4 核 8G 的容器里把 Tomcat 切到虚拟线程后同步接口的吞吐提升非常明显代码却不用改成响应式。3.3 异步场景下的正确姿势如果你的项目是 WebFlux 或需要全链路响应式那WebClient仍然不可替代。RestClient本身是同步的强行塞进响应式链路里会阻塞事件循环属于典型的错误用法。但 Spring Boot 3.4 里有一个很舒服的组合RestClientAsync 虚拟线程。业务层继续写同步代码把耗时操作丢给虚拟线程池执行整体复杂度远低于手写CompletableFuture加超时控制。这样做最大的收获其实是代码可读性。排查问题时同步代码的堆栈是一步步往下走的而响应式代码的堆栈经常被打散心智负担高不少。对大多数内部管理系统来说虚拟线程方案已经够用了。3.4 集成时的坑我在实际项目中踩到过几个细节问题列出来供参考一是超时配置。RestClient.Builder里配置 connectTimeout、readTimeout 的时候需要配合ClientHttpRequestFactorySettings使用建议显式声明别用默认值。默认的连接超时在很多场景下偏长接口依赖方一旦挂掉你的线程会被拖死。二是连接池复用。如果你直接new RestClient而没有借助 Builder 和连接池每次调用都会新建连接高并发下会看到大量 TIME_WAIT。正确的做法是注入RestClient.Builder由框架统一管理底层连接池。三是异常处理。RestClient对非 2xx 响应默认会抛异常根据业务需要你可以通过onStatus定制处理逻辑把 404、429 这些状态码映射成自定义业务异常而不是抛出一堆难以辨认的RestClientResponseException。4. 可观测性链路OTLP HTTP 传输与追踪配置的取舍4.1 3.4 中 OTLP 传输方式的变化这一节可能是很多人在升级后最容易“懵”的地方。Spring Boot 3.4 对 OpenTelemetry 的支持做了一个重要调整OTLP 的默认传输协议从 gRPC 改成了 HTTP/protobuf。也就是说如果你以前用的是 gRPC 导出链路数据升级后默认配置会走 HTTP 方式。为什么改因为 gRPC 依赖 HTTP/2很多内部网络、负载均衡器、防火墙对 HTTP/2 的支持并不好导致链路数据导出不稳定。而 HTTP/protobuf 只需要普通的 HTTP 就能传输部署友好度大幅提升。配置方式比较简单management.otlp.tracing.endpointhttp://collector-host:4318/v1/traces注意端口从 gRPC 默认的 4317 变成了 HTTP 默认的 4318。如果你原来的采集器只开了 4317 端口升级后链路数据会全部丢失千万别漏改。4.2 Micrometer Tracing 的接入细节链路追踪要真正落地光有 exporter 还不够你还得接入 tracer。在 Spring Boot 3.4 里依赖配好之后大部分工作由自动配置完成但有几个点需要手动确认。第一是引入正确的桥接依赖。用 OpenTelemetry 方案的话需要加入dependency groupIdio.micrometer/groupId artifactIdmicrometer-tracing-bridge-otel/artifactId /dependency第二是在代码里记录业务 span。框架会自动帮你把 HTTP 请求、数据库操作这些环节串成链路但如果你需要在某个方法上单独埋点可以用TracerAutowired private Tracer tracer; public void processPayment(PaymentRequest request) { Span span tracer.nextSpan().name(process-payment).start(); try (Scope scope tracer.withSpan(span)) { // 业务逻辑 span.tag(orderId, request.orderId()); } finally { span.end(); } }这段代码有个值得注意的点如果有人问我最容易被忽略的是什么我会说是跨线程传递。一旦你在子线程里执行任务默认的 traceId 是无法自动传递到新线程的。如果你用了Async或自定义线程池建议检查一下是否引入了micrometer-context-propagation依赖并正确配置否则你会在告警平台看到大量“断链”的 trace。4.3 生产环境可观测性落地建议关于采样率我的建议是核心交易链路全量采样非核心链路按 10% 采样。全量采样的成本在大多数企业里可以接受一旦出问题全量链路数据是排障的宝贵素材。按比例采样省下的成本远比不上一次线上事故排查耗时带来的损失。再一个容易被忽略的点是span 数量。如果业务代码里埋点过多一次请求可能产生几百个 span导入到链路平台后不仅费用高查询也慢。埋点要有目的性只对关键的跨系统调用、复杂业务方法做埋点而不是遍地开花。5. 数据库迁移Flyway 多数据库支持与配置思路5.1 为什么要多数据库支持先描述一个我见过的真实场景一套产品要卖给不同客户有的客户要求用 PostgreSQL有的要求 MySQL还有历史客户用 SQL Server。研发团队被迫在代码里写一堆方言兼容逻辑Flyway 脚本也要维护好几套。Spring Boot 3.4 里Flyway 的多数据库支持做了明显增强意思是你可以用一套迁移逻辑管理多个数据库实例每个实例有独立的迁移历史而不用每个数据库都去手工执行 SQL。在项目里配置多数据源加多 Flyway 时核心思路是每个数据源对应一个独立的FlywayMigrationStrategy或者一组独立的locations。比如spring.datasource.primary.urljdbc:postgresql://localhost:5432/main spring.datasource.secondary.urljdbc:mysql://localhost:3306/audit spring.flyway.locationsclasspath:db/migration如果你需要让不同的数据源用不同目录下的脚本可以进一步拆分配置。这里的重点是命名规范不同数据库的脚本目录不要混在一起否则版本号会互相冲突。我习惯把每个数据源对应的脚本放在独立子目录里比如db/migration/postgresql、db/migration/mysql一目了然。5.2 配置示例与实操心得下面是一个比较典型的双数据源加双 Flyway 配置片段# 主数据源 spring.datasource.primary.urljdbc:postgresql://localhost:5432/maindb spring.datasource.primary.usernameapp spring.datasource.primary.passwordsecret # 审计数据源 spring.datasource.secondary.urljdbc:mysql://localhost:3306/auditdb spring.datasource.secondary.usernameaudit spring.datasource.secondary.passwordsecret # 主库迁移脚本 spring.flyway.primary.locationsclasspath:db/migration/postgresql spring.flyway.primary.baseline-on-migratetrue # 审计库迁移脚本 spring.flyway.secondary.locationsclasspath:db/migration/mysql spring.flyway.secondary.baseline-on-migratetrue关键配置是baseline-on-migratetrue。这个参数的意思是如果目标数据库已经有表结构但还没有 flyway_schema_history 表Flyway 会把当前状态作为基线不会从头执行全部脚本。这个开关对于老项目接 Flyway 极其重要不开的话会直接报错让你误以为脚本写错了。另一个常用参数是spring.flyway.validate-on-migrate默认是 true。一旦你改过已执行脚本的内容校验就会失败。很多人第一反应是把这个校验关掉我的建议是别关。校验机制是 Flyway 防止“脚本被偷偷改动”的防线遇到校验失败正确做法是新增一个 V 版本脚本去修正而不是改动历史脚本。5.3 迁移脚本管理经验脚本命名规范建议严格遵守V版本号__描述.sql的格式版本号用数字递增描述用下划线分隔。比如V1__create_user_table.sql V2__add_order_index.sql V3__insert_default_config.sql这里有个容易忽略的细节版本号不要跳号也不要回退。如果已经执行到 V7突然冒出来一个 V6 的脚本Flyway 会拒绝执行因为历史版本不能穿插。正确做法是继续编 V8哪怕 V8 只是修一个错别字级别的数据修复也遵循追加原则。多数据库支持带来的另一个好处是同库多 Schema 的场景也能比较优雅地处理。比如 PostgreSQL 里不同业务共用实例但用不同 schemaFlyway 可以针对每个 schema 配置一套 locations 和迁移历史减少 schema 之间的耦合。6. 升级到 3.4 前这四件事我建议你提前确认6.1 配置属性变更盘点Spring Boot 每次大版本都会调整一批配置属性3.4 也不例外。最稳妥的办法是拿着官方发布的 configuration migration 对照表把你项目里所有spring.*、management.*、server.*前缀的配置统一过一遍。我遇到过比较典型的例子是某些属性被弃用后虽然启动不报错但打印了 deprecation warning效果也不再生效。如果你没注意 warning很可能上线后才发现某个配置没起到作用排查起来非常费劲。所以升级流程里一定包含这步项目启动时完整扫一遍日志把所有的 deprecation warning 收集起来逐条确认替代配置。6.2 第三方库与 Spring Cloud 版本兼容这一条算是大版本升级的“隐形门槛”。Spring Boot 3.4 一发布并不意味着你的所有依赖都能立刻跟上。常见的第三方库比如 MyBatis、Sa-Token、某些国产中间件客户端、以及 Spring Cloud 体系都需要各自发布适配版本。升级前务必先确认这四件事Spring Cloud 对应版本是什么是否已经发布。你使用的 MyBatis Starter 是否兼容 Spring Boot 3.4。链路追踪、分布式事务等横切组件是否有版本冲突。是否有团队自研的 starter 基于旧版 Spring Boot 构建需要同步发版。建议的升级顺序是先升父工程版本再锁定依赖版本然后本地编译最后逐步替换废弃 API。不要指望一天升完留出至少半天到一天的回归时间。6.3 我的升级清单与操作思路下面是我自己整理的一份升级 checklist每次大版本升级我都会照着过一遍拉一份当前项目的依赖树记录所有依赖版本。搜索代码中是否直接依赖 Spring Boot 管理的组件若有检查是否需要覆盖版本。全局搜索ConfigurationProperties绑定的配置项逐个和官方配置变更对照表核对。用最新版本启动应用重点观察启动日志里的 deprecation warning。跑一遍核心接口的基础测试包括 JSON 序列化结果和异常响应结构。检查日志平台和链路追踪平台的接入方式是否需要调整尤其注意 OTLP 端口变化。在测试环境压测一轮关注连接池、线程池、GC 行为是否有异常。最后有一个屡试不爽的经验升级结束后去官方文档的 “new features” 和 “upgrade notes” 部分再读一遍花半小时划重点比事后出问题再搜答案高效得多。很多细节官方都写了只是没人愿意看。说实话Spring Boot 3.4 这次更新并不会有“哇我代码写法全变了”的震撼但它切切实实把很多日常开发里的痒点解决了。结构化日志、RestClient 的成熟、OTLP HTTP 传输、多数据库迁移支持每一项都是拿来就能用、用了能少踩很多坑的东西。我在升级之后最大的感受是日志排查顺畅了链路追踪不再断链HTTP 调用代码也清爽了一大截。如果你最近正在纠结要不要升级我的态度很明确测试到位就升别拖。