☰
Spring Boot 3.4升级实战:版本基线、依赖冲突与兼容性排查指南
2026/10/10 4:17:33 网站建设 项目流程

Spring Boot 3.4.x 发布后,我第一时间把手头几个服务从 3.3.x 升上去。本来以为就是改个版本号、跑一遍构建的事,结果真正跑起来才发现,Spring Boot 3.4 背后的依赖基线已经全部换过一轮:Spring Framework 6.2、Jackson 2.18、Hibernate 6.6,连 Actuator 端点的权限模型都改了。最难受的是,这些坑不是在编译期报出来的,全部都是服务启动或者调用接口时才炸。

这篇踩坑记录是给正在升级或者打算升级 Spring Boot 3.4.x 的朋友看的。内容不是官方文档的翻译,而是我在实际升级过程中遇到的报错现象、排查链路和最终解决方式,也包括一些现在还在追踪的边界问题。如果你也在升级时遇到奇怪问题,可以直接按章节对照排查,不用把 Stack Overflow 翻到底。

1. 升级前先算好账:版本基线与依赖树检查

很多朋友看到“3.4”就默认是小版本号,直接改 parent。这个思路在 2.6 升 2.7 时问题不大,但从 3.3 升 3.4 就不能只看版本号了。Spring Boot 3.4 的底层框架版本有明确提升,如果有些依赖没跟上,启动时才会炸。

1.1 先确认 JDK、构建工具、微服务组件的版本底线

Spring Boot 3.4.x 和之前的 3.x 一样,底线是 JDK 17。如果你的项目还在用 JDK 8 或 11,那就别急着升级,先把 JDK 升上来。有条件的话直接用 JDK 21,我在升级时发现不少三方库(比如 Lombok、MapStruct、springdoc)在 JDK 21 下的兼容性普遍好于 JDK 23,JDK 23 也能跑,但为了稳,生产环境我最后都落在 JDK 21 上。

构建工具这块容易被忽略。Maven 至少要 3.6.3,Gradle 要求 7.6.4 或 8.4 以上。我接触过一个比较老的模块,一直用 Gradle 8.2,升级 Boot 3.4 时构建直接报错,提示 Spring Boot Gradle plugin 需要更高的 Gradle 版本。这种事不用硬扛,直接把 Gradle wrapper 升到 8.14 就行。如果项目同时用了 Spring Cloud,还要注意 Spring Boot 3.4 对应的是 Spring Cloud 2024.0.x,旧版 Spring Cloud 2023.0.x 最好不要混用,虽然部分功能能跑,但服务发现、配置中心的适配会出问题。

下面这个表是我基于这次升级整理出来的版本参考:

组件版本要求(以 3.4.x 为参照)
JDK17+,推荐 21 LTS
Maven3.6.3+
Gradle7.6.4 / 8.4+
Spring Framework6.2.x(由 Spring Boot BOM 决定)
Spring Cloud2024.0.x
Lombok1.18.34 及以上
springdoc-openapi2.7.0 及以上

1.2 不要盲目改代码,先读一遍依赖树

升级大版本最容易出现的其实是依赖冲突,而不是代码问题。我习惯先把根 POM 或 build.gradle 的 Spring Boot 版本改到 3.4.x,然后跑一遍依赖树:

mvn -U dependency:tree

Gradle 项目用:

./gradlew dependencyInsight --dependency <groupId>:<artifactId>

重点看spring-core、spring-webmvc、spring-boot-autoconfigure是否出现多个版本。如果某个第三方 starter 还停留在 Spring Boot 2.x 时代的坐标,BOM 管不住它,就很容易把 Spring 类带偏。我遇到过最典型的场景:某个内部框架做了一个自定义 starter,在 jar 里用spring.factories注册自动配置,升级后控制台会打印一行 WARN:

The auto-configuration imports should be written in AutoConfiguration.imports

这行提示不是 error,但你必须注意。Spring Boot 3.4 虽然还能兼容旧的spring.factories注册方式,但自动配置的加载顺序和优先级已经变了,某些 Bean 初始化时机可能跟以前不一样。最好的处理是把自己 jar 里的META-INF/spring.factories改成META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports,里面一行一个自动配置类全限定名。这个改造不复杂,但能帮你省掉后面排查“Bean 为什么没注入”的不少时间。

2. OpenAPI 集成的兼容性事故:接口文档从打不开到恢复

升级到 Spring Boot 3.4 之后,我遇到的第一批问题里就有 springdoc-openapi。这个坑出现概率极高,而且报错方式很多样,单看报错很难和“版本不兼容”联系在一起。

2.1 现象与报错

现象有两种。一种是一启动就抛NoSuchMethodError,日志里经常能看到org.springframework.web.servlet.mvc.condition.PathPatternsRequestCondition相关方法找不到;另一种是服务能正常启动,但访问/swagger-ui.html时页面空白,或者接口列表一直加载不出来,控制台有Failed to start the 'documentation' service之类的提示。

我当时第一反应是“springdoc 配置错了”,来回检查了半天也没发现问题。后来把 springdoc 的版本从 2.5.0 改成 2.7.0,启动立刻正常。这不是个例,因为 Spring Framework 6.2 对spring-webmvc内部的条件请求结构做了调整,springdoc 旧版编译时依赖的类已经对不上了。

2.2 根本原因:Spring Framework 6.2 内部 API 变化

不需要完全搞懂 Spring MVC 内部类的每个细节,你只需要记住:springdoc-openapi 老版本会通过反射或直接引用 Spring MVC 内部条件类来收集接口元数据,而 Spring Framework 6.2 调整了这些内部类的位置和签名。旧版 jar 在运行时找不到对应类或方法,就会产生NoClassDefFoundError或NoSuchMethodError。

这类错误不会在编译期暴露,因为 Spring Boot 3.4 的 BOM 并没有强制约束 springdoc 的版本。你的依赖树里如果还留着 2.5 / 2.6,运行期就会炸。所以排查时先检查 springdoc 版本,别一上来就怀疑 OpenAPI 配置本身。

2.3 解决步骤:升级 springdoc 并把 swagger 依赖统一

最直接的办法是升级。Maven 项目把 springdoc-openapi starter 版本提升到 2.7.0 以上:

<dependency> <groupId>org.springdoc</groupId> <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId> <version>2.7.0</version> </dependency>

如果你的项目是 WebFlux,对应使用springdoc-openapi-starter-webflux-ui,版本号也是一样的。升级后记得重新访问一次接口文档,确认 Swagger UI 的“try it out”能正常发起请求。

升级后还有一个小坑要注意:如果项目里同时引了swagger-annotations和swagger-core,需要统一版本。我曾经在一个模块里看到这两个 jar 的版本一个是 2.2.x、一个是 2.1.x,结果接口分组信息显示混乱。把它们统一到 2.2.22 或者更新的 2.2.x 版本即可。另外,如果在 springdoc 里配置了多分组,比如springdoc.group-configs[0].packages-to-scan,注意检查分组名里有没有以/开头,这种分组名在 2.7 下访问元数据接口时可能返回 404,去掉开头斜杠就正常了。

3. Actuator 端点权限模型升级:health 接口 404 的排查链路

如果你的项目用了spring-boot-starter-actuator,升级到 3.4 后大概率会遇到监控端点访问异常的问题。这个坑比 OpenAPI 更隐蔽,因为报错不明显,就是某个接口突然 404 了。

3.1 现象:监控信息少了,health 也访问不了了

升级后访问/actuator/health,可能直接返回 404,或者有响应但看不到components和details信息。另一个常见表现是/actuator/health/liveness和/actuator/health/readiness这两个 Kubernetes 探针地址不再生效,返回 404。

我一开始以为是自己暴露配置被环境变量覆盖了,查了一圈发现management.endpoints.web.exposure.include里明明有health。后来才注意到,Spring Boot 3.4 对端点权限模型做了调整:原来的“是否暴露 + 是否启用”两层模型之外,现在端点本身还有一层access属性。

3.2 根因:exposure 和 access 是两回事

简单理解,exposure.include决定端点要不要挂在 Web 路径上,而access决定端点暴露出来之后是否可读写。健康检查端点在 3.4 里的默认行为和旧版本不完全一致,如果只配了 include,没有给 health 设置 access,某些监控脚本就会访问受限。

我最终的配置类似这样:

management: endpoints: web: exposure: include: health,info,metrics endpoint: health: access: unrestricted show-details: always probes: enabled: true

这里的access: unrestricted是在“不引入 Spring Security 做精细鉴权”的前提下,让健康检查恢复为旧版的可读可看行为。如果你的生产环境要求更严格,可以改成read-only,或者结合 Spring Security 做路径级放行。

3.3 K8s 探针路径的坑

Spring Boot 3.4 对健康探针新增了/actuator/livez与/actuator/readyz两个路径,同时保留老的/actuator/health/liveness与/actuator/health/readiness。如果你的部署脚本还用老路径,可能一切正常;但如果你为了“新特性”把探针改成了新路径,就要确认两件事:management.endpoint.health.probes.enabled=true已经打开,以及add-additional-paths的配置是否符合预期。

我在项目里就吃过这个亏。K8s 的 deployment yaml 里把 livenessProbe 改成了/actuator/livez,但add-additional-paths没开,探针一直失败,服务被不断重启。最后把 yaml 改回老路径,同时保留probes.enabled=true才稳定下来。

另外,日志里如果出现Exposing 6 endpoint(s) beneath base path '/actuator'这行信息,只代表暴露列表正常,不代表权限模型正常。升级后建议把常用的/actuator/health、/actuator/info、/actuator/metrics都实际请求一遍,不要只看日志。

4. 持久层连环坑:Hibernate 6.6 命名策略与 MyBatis Starter 版本

持久层是升级时最容易出现“运行期才报错”的重灾区。Boot 3.4 把默认的 Hibernate 版本提到了 6.6.x,同时 MyBatis Starter 也要切到 3.x 分支。这两条线都不注意的话,项目启动能把你绕晕。

4.1 JPA:命名策略与历史表结构冲突

Spring Boot 3.4 默认带的 Hibernate 版本是 6.6.x。如果你是从 3.2 / 3.3 升上来的,整体行为大体一致,但有一些默认值调整。最容易踩到的是物理命名策略和保留字处理。

举个例子:实体里有一个字段dataSource,数据库表里也建了data_source列。旧配置里你可能依赖默认策略直接将dataSource映射为data_source;但某一小版本调整后,启动时 Hibernate 对 DDL 的自动校验可能会生成完全不同的列名。如果表结构是历史遗留手动建的,列名是dataSource(驼峰),就会出现column 'data_source' not found这类报错。更隐蔽的是,用 JPA Specification 或findAll查询时,生成的 SQL 列名与实体字段不一致,运行期才抛异常,上线前测试根本发现不了。

解决方式不是到处补@Column(name = "..."),而是先统一命名策略。如果项目约定数据库列就是下划线风格,直接把 Spring Boot 默认策略显式声明出来:

spring: jpa: hibernate: naming: physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy

如果有些表是反向工程生成的实体,字段本身就是下划线,但 JPA 属性想用驼峰,千万别混着来。我的建议是:JPA 实体里所有非标准映射都显式写@Column,不要依赖全局策略。这个建议在 Hibernate 5 时代听上去很保守,但 Hibernate 6 之后属于基本要求。

4.2 MyBatis:Starter 还在 2.x?

另一个项目用 MyBatis,升级 Spring Boot 3.4 后启动报MapperScan相关错误,或者SqlSessionFactory初始化失败,日志里能看到NoClassDefFoundError: org/apache/ibatis/session/Configuration。原因很简单:mybatis-spring-boot-starter的 2.x 分支面向 Spring Boot 2.x,不能直接用。必须使用 3.0 分支。

MyBatis 官方从 3.0.0 开始支持 Spring Boot 3.x。我一直在用的是 3.0.4,配 Boot 3.4 没有出现问题。

<dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>3.0.4</version> </dependency>

升级后要确认mybatis.mapper-locations和mybatis.type-aliases-package是否被配置中心正确读到。有一个隐蔽坑:单数据源时SqlSessionTemplate由自动配置搞定,没问题;但如果项目里有多个数据源并自定义了多个SqlSessionFactory,升级 Boot 3.4 后原来的默认自动配置可能会覆盖掉SqlSessionTemplate,导致@MapperScan注入时找不到唯一的 Bean。处理方式是给每个数据源单独指定SqlSessionFactory和TransactionManager,不要依赖默认行为。

4.3 HikariCP 参数校验变得更严格

Spring Boot 3.4 对 HikariCP 的配置校验也比以前严格。以前在spring.datasource.hikari.maximum-pool-size里写过数字字符串,或者idle-timeout的单位不明确,可能只是被忽略;升级后会直接启动失败,报错信息类似Failed to bind properties under 'spring.datasource.hikari'。

解决这个问题并不难,打开配置文件检查一下对应值的类型即可。如果找不到是哪些参数不合法,可以先临时打开 actuator 的 configprops 端点:

curl -H "Accept: application/json" http://localhost:8080/actuator/configprops

然后把不匹配的配置项改掉,查完再把这个端点收起来。

5. 测试注解的“软弃用”:@MockBean 和 @SpyBean 的迁移路径

升级到 Boot 3.4 之后,单测和@SpringBootTest里如果用了@MockBean,IDE 会在注解上画删除线。如果你的项目开了-Werror,构建甚至会直接失败。这个坑不影响运行,但会给后续升级埋下隐患。

5.1 现象:没有报错,但到处是 deprecated 警告

说实话,第一次看到这个警告时我并没有当回事,毕竟代码还能编译。后来重新审视才发现,Spring Boot 3.4 已经把@MockBean和@SpyBean标记为 deprecated 了。它们不是马上删除,但长期维护的项目越早迁越好,否则后续版本一升级,测试代码会成片挂掉。

控制台日志里可能出现类似这样的提示:

@MockBean is deprecated and may be removed in a future major version

5.2 新注解长什么样

Spring Framework 6.2 新增了@MockitoBean和@MockitoSpyBean,Spring Boot 3.4 把它们作为替代方案。迁移很简单,把 import 和注解名换掉:

旧写法:

import org.springframework.boot.test.mock.mockito.MockBean; @SpringBootTest class OrderServiceTest { @MockBean private InventoryClient inventoryClient; }

新写法:

import org.springframework.test.context.bean.override.mockito.MockitoBean; @SpringBootTest class OrderServiceTest { @MockitoBean private InventoryClient inventoryClient; }

@SpyBean对应@MockitoSpyBean,包路径和上面类似。改完之后测试行为没有任何变化,但 IDE 的下划线消失,构建也更干净。

5.3 新注解对同名 Bean 的处理更严格

有一点需要特别注意:新注解在“同一个测试类里 mock 同一类型的多个 Bean”时,不再像旧版那样自动匹配,推荐显式指定name属性。比如你有两个RestTemplate类型的 Bean:

@MockitoBean(name = "internalRestTemplate") private RestTemplate internal; @MockitoBean(name = "externalRestTemplate") private RestTemplate external;

如果不指定 name,Spring 容器在注入 mock 时会因为存在多个同类型 Bean 而报错。此外,Boot 3.4 自带的 Mockito 已经升级到 5.x 分支,如果项目里手动覆盖了旧版 Mockito 4.x,那么@MockitoBean可能无法正常工作。检查依赖树,把org.mockito:mockito-core的版本交给 Spring Boot BOM 管理即可。mockito-inline在 5.x 里已经合进了 mockito-core,之前单独加的也可以去掉了。

6. 序列化与配置迁移的隐性变化:LocalDateTime 格式和废弃属性警告

有些问题不会让服务启动失败,但会让接口返回的数据跟以前不一样,或者日志里多出一堆 WARN。这些“隐性变化”没有明显的 color,最容易被忽略。

6.1 LocalDateTime 序列化格式不对

Spring Boot 3.4 里默认的 Jackson 版本是 2.18。如果你是直接从 Spring Boot 2.x 升上来的,会明显感觉到LocalDateTime的默认输出格式“变了”。但实际不是 Jackson 变了,而是 Spring Boot 在 3.x 移除了以前的一些自动注册行为。最稳妥的方式是显式注册你想要的格式,不要依赖默认值。

例如下面这个配置,是我在多套项目里验证过可用的:

@Configuration public class JacksonConfig { private static final DateTimeFormatter DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); @Bean public Jackson2ObjectMapperBuilderCustomizer jsonCustomizer() { return builder -> { builder.serializers(new LocalDateTimeSerializer(DATETIME)); builder.deserializers(new LocalDateTimeDeserializer(DATETIME)); }; } }

如果只改spring.jackson.date-format发现没效果,不用奇怪。那个配置主要影响java.util.Date,java.time包需要单独处理。序列化问题虽然不会导致系统崩溃,但接口联调时字段格式对不上,会浪费很多时间。

6.2 启动日志里的 WARN 不是噪音

Spring Boot 3.4 对配置属性的校验更严格了。如果配置文件里有旧属性,启动日志会以 WARN 形式提示,比如Property 'spring.datasource.tomcat.max-wait' is deprecated。我建议把升级后的第一次启动日志重定向到文件,专门 grepdeprecated和WARN,然后逐条清理。

第三方 starter 还在用META-INF/spring.factories注册自动配置时,Boot 3.4 也会打印警告。这不会导致启动失败,但因为自动配置加载顺序可能变了,某些 Bean 初始化会晚于预期,出现类似“xxxRepository没有注入”的问题。排查时沿着spring.factories警告去找,把注册文件改成:

org.springframework.boot.autoconfigure.AutoConfiguration.imports

文件内容就是你自己的自动配置类全限定名,一行一个。改完重启,WARN 消失,注入问题也没了。

6.3 启动变慢先看 HikariCP 初始化重试

还有朋友说升级完启动变慢了,从原来的 5 秒变成 20 秒。这类问题很大程度不是 Spring Boot 本身变笨,而是数据源初始化时因为配置校验失败后反复重试。排查时重点看日志里有没有HikariPool-1 - Exception during pool initialization或者Connection is not available之类的记录。

如果数据库地址没变、连接串没变,先确认spring.datasource.hikari.initialization-fail-timeout参数。把它设小一些,能更快暴露真实原因。有些时候报的是“配置属性类型不合法”,实际上就是上一节讲的参数校验问题,改掉类型就恢复了。

7. 已知边界问题与下阶段踩坑预案

最后记录一些我目前还没有完全踩完、但已经知道存在风险的场景。这部分不是劝退,而是给准备升级的人打一个预防针。

7.1 还不能无脑升的组合

以下组合暂时不建议在核心系统上升级到 Spring Boot 3.4.x:

  • 还在用 Spring Boot 2.x 老配置风格,并且有大量自定义spring.factories的内部项目,先做迁移再升级;
  • 同时使用旧版本 springdoc(低于 2.7)和 WebFlux 网关的项目;
  • 使用 GraalVM Native Image 的项目,Boot 3.4 对 AOT 有改进,但第三方库的 metadata 更新不太均衡,需要额外跑一次完整的 AOT 测试;
  • 生产环境 JDK 还是 8 或 11 的,必须先把 JDK 升到 17 以上再说。

这些组合不一定全部出问题,但排查成本高。我自己的策略是先拿边缘模块试,确认没问题再推核心模块。

7.2 升级后必查清单

按目前遇到的情况,我给自己整理了一份“升级后必查清单”,每次升级新小版本都会过一遍:

  • 依赖树里是否出现多个版本的 Spring、Jackson、Hibernate;
  • 启动日志是否出现deprecated或spring.factories警告;
  • Actuator 常用端点的实际 HTTP 状态码是否符合预期;
  • OpenAPI 页面能否正常加载,多分组接口能否正常合并;
  • 测试套件里@MockBean/@SpyBean的使用位置,以及迁移计划;
  • JPA 实体的命名策略、MyBatis XML 里的 resultType 是否全部显式;
  • 序列化格式是否和升级前一致,尤其是LocalDateTime。

这份清单不是固定死的,后面遇到新坑我还会继续往里补。如果你恰好在某个问题上卡了很久,可以按章节先对照一下,很多问题其实就是依赖版本没对齐而已。

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

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

立即咨询