☰
SpringBoot SpringCloud SpringFramework版本对应关系与迁移实战指南
2026/9/26 7:57:00 网站建设 项目流程

如果你手头正在维护一个 Java 后端项目,或者刚接手别人留下一堆“能跑但没人敢动”的历史代码,那你迟早会和“SpringBoot、SpringCloud、SpringFramework 三者版本对应”这件事撞个满怀。它不是面试里背出来的知识点,而是每次新建工程、每次升级依赖、每次半夜被拉起来排查线上问题时都得面对的现实。最典型的翻车现场我见过太多:编译期风平浪静,启动期突然NoSuchMethodError,一看 pom,有人把spring-boot-starter-parent从2.7.18升到了3.2.x,而 Spring Cloud 还停留在2021.0.x的旧列车上。

这篇文章就围绕这条主线,把三件事讲透:三者之间到底按什么规则互相绑定、你怎么给现有项目做一次版本体检、以及从 Boot 2.7 往 Boot 3.x 迁移时到底该按什么顺序排查。思路对所有 Spring 系开发都适用,不管你是刚毕业的新人,还是带团队做升级的老手,手里有这张对应表,至少能避免一半的依赖噩梦。

1. 一张表看懂三者的版本绑定关系

1.1 Spring Boot 和 Spring Framework 的版本是谁在决定

很多新人写代码的时候根本感知不到 Spring Framework 的存在,因为spring-boot-starter-web会把spring-core、spring-webmvc这些依赖自动带进项目里。但你心里必须清楚一个底层事实:Spring Boot 并没有重写 Spring Framework,Boot 只是在 Framework 之上做了一层自动装配和 start 依赖封装,同时把几百个第三方库的版本统一管理进自己的 BOM 里。换句话说,你改 Boot 版本号,本质上是在切换一套“官方测试过的依赖集合”,而不是单纯换个启动器。

所以 Boot 和 Framework 之间的关系不是“建议对应”,而是“被 Boot 写死”。Spring 官方每个 Boot 版本都对应一个确定的 Framework 小版本线,这张表我整理如下:

Spring BootSpring Framework基准 JDK
1.5.x4.3.x7 / 8
2.0.x5.0.x8
2.1.x5.1.x8
2.2.x5.2.x8 - 13
2.3.x5.2.x8 - 14
2.4.x / 2.5.x5.3.x8 - 16
2.6.x5.3.x8 - 17
2.7.x5.3.x8 - 17
3.0.x / 3.1.x6.0.x17+
3.2.x6.1.x17 - 21

JDK 那一列写的是官方保证过的范围,不代表超出范围一定跑不了,只是不在兼容性承诺内。比如 2.7.18 这个 2.7 线的最后一个补丁版本,底层用的就是 Spring Framework 5.3.31,你在 JDK 17 上跑没问题,在 JDK 19、20 上也能凑合,但出了问题官方不会认账。

记住两条最关键的分界线:2.7.x 是 Spring Framework 5.3 这条线的最后一站,3.0 开始框架版本直接跳到 6.0.x。为什么跳?因为 Boot 3.x 把 Java EE 的javax.*命名空间整体换成了jakarta.*,这是 API 层面的破坏性变更,版本号必须大幅前进才能体现出来。

1.2 Spring Cloud 发版列车:为什么版本号是年份

Spring Cloud 和前面两个不太一样,它不是单个框架,而是一堆组件合集的统称,里面包含 gateway、openfeign、config、consul、eureka、loadbalancer 等一大堆子项目。因此它的版本号用“发版列车”(Release Train)来管理,一个版本号对应一整套组件的统一发布节奏。

早期 Spring Cloud 的版本号是伦敦地铁站名,比如 Dalston、Edgware、Finchley、Greenwich、Hoxton,每个名字代表一条发版线。从 2020 年起官方改成年份号,命名格式变成2020.0.x、2021.0.x这样的三位结构,后面依然保留一个对应的地铁站名作为代号。举个例子:2023.0.x的代号是 Leyton,2022.0.x的代号是 Kilburn。这种命名看起来很花哨,但核心作用只有一个:让你清楚地知道这一整套云组件是在哪个 Boot 版本之上编译和测试的。

Spring Cloud 与 Boot 的对应关系,官方文档写得非常明确:

Spring Cloud 版本兼容 Spring Boot对应的 Framework
Dalston / Edgware1.5.x4.3.x
Finchley2.0.x5.0.x
Greenwich2.1.x5.1.x
Hoxton2.2.x / 2.3.x5.2.x
2020.0.x (Ilford)2.4.x / 2.5.x5.3.x
2021.0.x (Jubilee)2.6.x / 2.7.x5.3.x
2022.0.x (Kilburn)3.0.x / 3.1.x6.0.x
2023.0.x (Leyton)3.2.x6.1.x

注意,Spring Cloud 一行往往对应 Boot 的两个小版本线,比如2021.0.x同时兼容 Boot 2.6 和 2.7。这说明兼容性是有一定弹性的,但弹性仅限于同一条大版本框架线内。如果你把 Boot 升到 3.x,Spring Cloud 还停在2021.0.x,这条弹性就彻底断裂了。

2. 版本错位为什么总是“编译能过,运行才炸”

2.1 BOM 把版本藏起来,也把风险藏起来

很多团队对版本对应不敏感,是因为 Maven 的 BOM 机制太方便了。你在spring-boot-starter-parent里定义一个 Boot 版本,它就自动帮你管住 spring-core、jackson、tomcat 等一切依赖的版本。Spring Cloud 那边也是同样的玩法,通过引入spring-cloud-dependencies这个 BOM,把 openfeign、gateway 等组件的版本全部锁住。

方便是真的方便,但副作用也很明显:pom 里看不到版本号,很多人就以为版本不重要,或者以为“只要用了 BOM 就万事大吉”。实际上 BOM 只负责管理依赖版本,它不负责替你检查组合的合理性。你完全可以在 Boot 3.2.x 的工程里强行引入spring-cloud-starter-gateway的旧版 starter,Maven 一样能把 jar 拉下来,编译也一样能过。

危险恰恰发生在运行期。Spring Cloud 的组件在编译时是针对某个特定 Boot 和 Framework 版本编译的,类的方法签名、接口结构都以那个版本为准。当 Boot 升级后,Framework 的内部实现变了,老组件调用的某个方法签名可能已经不存在,运行时 JVM 一加载就抛NoSuchMethodError。这个异常不是业务代码里的逻辑错误,而是类路径里同时存在两套互不兼容的类定义导致的。

2.2 最典型的三个翻车现场

第一种翻车方式是 JVM 版本不匹配。Boot 3.x 打出来的 jar 是用 JDK 17 编译的,class 文件的主版本号是 61。如果你还在用 JDK 8 运行,JVM 加载类的时候会直接抛出UnsupportedClassVersionError: Unsupported major.minor version 61.0。这个报错非常直白,一看就知道是运行环境太老。

第二种是前文提到的NoSuchMethodError。它的迷惑性最大,因为异常堆栈里的类名和方法名看起来都是你自己项目里熟悉的类,实际上抛异常的却是 Spring 内部的某个类。例如你在 Boot 3.2 工程里用旧版 Spring Cloud,启动时日志里出现NoSuchMethodError: org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext之类的错误,基本可以断定是 Boot 和 Cloud 错位了。

第三种是命名空间缺失。Boot 3.x 里 servlet 相关的 API 从javax.servlet换成了jakarta.servlet,如果你项目里还引着 Boot 2 时代的第三方 starter,它内部硬编码引用的javax.servlet.Filter等类根本不存在,启动时就是NoClassDefFoundError。这种问题改代码都没用,必须先换掉不兼容的 starter 版本。

经验之谈:遇到启动期异常,先别急着看业务代码,第一步永远是看异常的类加载来源。确定是哪个 jar 抛出来的,再倒推它的版本号和当前 Boot 的兼容性。

3. 实操:给你的项目做一次完整版本体检

3.1 用依赖树把真实版本扒出来

接手一个老项目时,第一件事不是看业务代码,而是把依赖树导出来。命令很简单:

mvn dependency:tree -Dincludes=org.springframework:spring-core

spring-core的版本号就是当前 Spring Framework 的真实版本。比如输出里显示org.springframework:spring-core:jar:5.3.31,说明底层框架是 5.3.31,对应 Boot 2.7.x 这条线。

接着查 Spring Cloud 的组件版本:

mvn dependency:tree -Dincludes=org.springframework.cloud

这里会列出所有 cloud 组件的版本号。如果主工程里没有显式声明 Spring Cloud BOM,而是靠某个自定义 parent 带进来的,建议再跑一条更完整的:

mvn dependency:tree -Dverbose -Dincludes=org.springframework.cloud

-Dverbose会显示出依赖是被谁引入的、版本被哪个规则仲裁覆盖,看冲突非常有用。我一般会把输出重定向到文件里保存下来:

mvn dependency:tree > deps-before.txt

升级完成后重新导一份,两份文件做 diff,就能精确知道这次升级到底动了哪些依赖。

3.2 怎么判断当前组合是否合法

假设体检出来的结果是:spring-core是 6.1.x、Spring Cloud 是2023.0.x,那这个组合是干净的,因为 6.1.x 对应 Boot 3.2.x,2023.0.x列车也正好支持 Boot 3.2。

但如果spring-core显示 6.1.x、Spring Cloud 还是2021.0.x,那就要警惕了。2021.0.x列车里的组件是基于 Framework 5.3 编译的,在 6.1 的运行时里很可能出现抽象方法签名不一致。最稳妥的办法是把 Spring Cloud 升到2023.0.x,而不是去把 Boot 拉回 2.7。当然,如果项目确实因为某些原因暂时升不了 Boot,那就得反过来让 Boot 和 Cloud 同时保持在 2.7 +2021.0.x这套组合里。

还有一个判断技巧:直接看本地 Maven 仓库里spring-cloud-dependencies对应版本的 pom 文件。路径是~/.m2/repository/org/springframework/cloud/spring-cloud-dependencies/版本号/,打开里面的 xml,搜spring-cloud-dependencies-parent的版本,再反查那个 parent 是在哪个 Boot 版本上构建的。这个办法笨一点,但特别适合排查那些“官方文档没写清楚”的边缘组合。

3.3 用 parent 和 BOM 锁版本,别手工钉死组件版本

我在不少项目里见过这种写法:pom 中显式声明了spring-cloud-starter-gateway的版本号,比如3.1.1。这种做法的隐患在于,Gateway 组件的小版本跟随 Spring Cloud 列车的节奏发布,你手工钉死的版本很容易和 Boot 升级脱节。

正确的姿势是只声明 Spring Cloud BOM,子组件一律不写版本:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.2.5</version> <relativePath/> </parent> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>2023.0.3</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> </dependencies>

这样 Boot parent 管住 Framework 和基础三方库,Spring Cloud BOM 管住云组件,两者各管一摊、互不越界。你要是手贱在子组件上又加了一个版本号,等于亲手撕开了 BOM 的保护,后面踩的坑全部自付。

3.4 连带版本:JDK、Jackson、小程序员都跟着变

版本对应不只是三兄弟之间的事,它还会波及其他依赖。最典型的就是 Jackson 和 JDK 的关系。

Boot 的 BOM 里通过jackson-bom统一管理 Jackson 2.x 的版本,正常情况下你完全不需要在 pom 里显式声明 Jackson。但在 JDK 17 上跑老项目时你会发现,如果有人手写了 Jackson 2.9 这种老版本,反射访问一些模块会遇到InaccessibleObjectException,因为 JDK 17 默认禁止了对内部模块的深度反射。同理,如果你的接口返回LocalDateTime报InvalidDefinitionException,那是jackson-datatype-jsr310没有被正确引入,而不是 Jackson 本身的 bug。

第三方 starter 也是重灾区。Boot 3 出来后,mybatis 官方把 starter 升级为mybatis-spring-boot-starter3.0.x 版本,只有这个版本才兼容 jakarta 命名空间。像 Druid、分页插件、Flowable、Activiti 这些项目也有各自的 Boot 3 适配版本。接入前务必先查该组件的官方版本说明,别因为“之前 2.7 就是这么配的”就直接照搬。

血的教训:我见过一个项目升级 Boot 3 之后,mybatis 的老 starter 把javax.persistence相关的类带进了 classpath,导致启动时 Hibernate 的实体扫描直接报错,最后定位了两天才发现是 starter 版本不匹配。

4. 升级实录:从 Boot 2.7 迁到 3.2 的版本核对清单

4.1 升级前先想清楚版本目标

网上经常会看到“springboot 版本太高”之类的吐槽,听起来好像版本越高越容易出问题。但我的观点是:版本本身没有高不高,只有配套不配套。Boot 3.2 要求 JDK 17,要求 Servlet 规范切到 Jakarta EE,要求 Spring Cloud 列车换到2023.0.x。这些条件如果团队都满足,3.2 是很稳的一个选择;如果团队还守着 JDK 8,那硬上 3.2 就是灾难。

所以升级之前先把目标版本定死,我建议直接用 Boot 3.2.x 这一条线,它是 3.x 系列里比较成熟的稳定线,配套的 Framework 是 6.1.x,Spring Cloud 对应2023.0.x。如果你手里的项目体量大,不想一次性跨两个大版本,也可以考虑先在 2.7 这条线上把 Spring Cloud 升到2021.0.x的最终补丁,把代码里所有已废弃的 API 清一遍,再整体切到 3.2。

4.2 javax 到 jakarta 的迁移清单

从 2.7 升 3.2 最大的代码改动不是 POM,而是全工程范围内的 import 替换。Java EE 8 时代留下的javax.*命名空间,在 Jakarta EE 9 里全部改成了jakarta.*。我整理了一个对照表:

旧命名空间新命名空间典型类
javax.servletjakarta.servletFilter、Servlet、ServletRequest
javax.persistencejakarta.persistenceEntity、Table、Column
javax.validationjakarta.validationValid、NotNull、Validated
javax.annotationjakarta.annotationPostConstruct、PreDestroy
javax.transactionjakarta.transactionTransactional 的注解定义
javax.ws.rsjakarta.ws.rsREST 相关标注

注意@Transactional这个坑:Spring 自己的org.springframework.transaction.annotation.Transactional不用改,但如果你在代码里用过javax.transaction.Transactional,就必须换成jakarta.transaction.Transactional。一个字符之差,行为完全一样,但类归属不同,写反了编译都不报错,运行期事务可能莫名其妙失效。

全局替换的时候别用 IDE 盲替换“javax”为“jakarta”,那会把javax.crypto这类 Java 标准库的引用也换坏。正确的做法是只替换javax.servlet、javax.persistence这几个明确属于 Java EE 的包前缀。

4.3 Spring Security 和自动配置的变化

如果你项目里用了 Spring Security,升级到 Boot 3.2 之后还要面对WebSecurityConfigurerAdapter被移除的问题。这个类在 Spring Security 5.7 就开始废弃,6.0 整个删掉了。旧代码里凡是继承这个 adapter 重写configure(HttpSecurity)的写法,都要改成声明SecurityFilterChainBean 的 Lambda 风格。同时antMatchers()也改名成了requestMatchers(),这些细节不提前处理,升级后编译直接失败。

自定义自动配置的注册方式也变了。Boot 2.7 时代,自定义 starter 在META-INF/spring.factories里注册自动配置类;Boot 3 改成了META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports这个文件。如果你的项目里自己写过 starter,这个文件必须跟着迁移,否则自动配置静默失效,项目跑起来功能缺一堆还找不到原因。

4.4 升级完成后的三重验证

第一步,编译和测试。这一步只能验证代码层面的兼容性,验证不了运行期的依赖冲突。

第二步,导出全量依赖树 diff。升级前我存了deps-before.txt,升级后再导一份deps-after.txt,重点看这些变化:spring-framework是否从 5.3 跳到 6.1、tomcat-embed-core是否从 9.x 变成 10.1、jakarta.*相关 jar 是否出现。Tomcat 从 9 到 10.1 这一步是隐藏的陷阱,因为 Boot 3 内嵌的 Tomcat 已经遵循 Jakarta 命名空间,如果你的服务要打成 war 包部署到外部容器,目标 Tomcat 必须是 10 及以上,9 的小版本直接带不起来。

第三步,启动服务观察日志。升级后的第一次启动,要重点盯日志里的 WARN 和 ERROR,特别是 Spring Cloud 组件打印的版本兼容性警告。有些 Spring Cloud 版本启动时会在日志里提示你“当前 Boot 版本不在本列车测试范围内”,这个警告即使不阻止启动,也别视而不见。

5. 常见错误与排查技巧速查

版本对应的问题虽然五花八门,但归纳起来不外乎下面这几种。我做了一张速查表,排查时按图索骥效率会高很多:

现象可能原因排查方法
启动报UnsupportedClassVersionErrorBoot 3.x 的 class 版本需要 JDK 17java -version检查实际运行 JDK
启动报NoSuchMethodErrorSpring Cloud 组件与 Boot/Framework 版本错位检查 spring-core 版本与 cloud 版本是否匹配
报NoClassDefFoundError: javax/servlet/...Boot 3 下还引用了 javax 时代的旧 starter搜 pom 里绑死旧组件的显式版本
反射报InaccessibleObjectException老版本 Jackson 等库在 JDK 17 深度反射升级组件,或让 BOM 管理版本
LocalDateTime反序列化失败缺jackson-datatype-jsr310检查是否有人覆盖了 jackson 版本
某个自动配置没生效spring.factories没迁到.imports文件检查 META-INF/spring 目录
OpenFeign 调用报 loadbalancer 初始化异常Ribbon 被移除,需要 spring-cloud-loadbalancer确认 openfeign 和 cloud BOM 对齐

排查NoSuchMethodError时有个很硬核的技巧:直接看某个类的编译版本,判断它到底是哪个 JDK 编译出来的产物。

javap -verbose -classpath 你的jar包路径 org.springframework.core.SpringVersion | grep "major"

输出里的 major 版本号对应关系是:52 表示 JDK 8,55 表示 JDK 11,61 表示 JDK 17。如果发现 Spring 的核心类 major 是 61,而你项目声明的是 Boot 2.7,那说明 classpath 里混进了 Boot 3 的 jar,版本仲裁出了问题。这种时候去查spring-boot-dependencies的 pom 属性比翻代码有用得多。

这个技巧对排查“编译没问题但运行期行为诡异”的情况特别有效,建议复制到自己的笔记工具里。我在处理多个项目依赖混乱的问题时,靠这个命令定位过至少三次“幽灵版本”问题。

6. 最后说点个人习惯

版本对应这件事,本质上不是什么玄学,就是一套“官方测试过的组合”和“你自己拼凑的组合”之间的边界。Spring Boot、Spring Cloud、Spring Framework 三者之间的关系,官方文档其实写得很清楚,大多数人栽跟头不是因为查不到,而是因为根本没想着要查。

我自己现在的习惯是:新建项目时先定 Boot 版本,再按官方对应表选 Spring Cloud 列车,其他所有第三方组件默认交给 BOM 管理,打死不手写版本;接手老项目时先跑一遍mvn dependency:tree,把结果文件提交到仓库里,作为依赖治理的基线;每次升级完必做依赖树 diff,确认 framework、tomcat、jakarta 这些关键坐标都按预期前进。

如果你正在面对一个“说不出为什么能跑”的老项目,我最大的建议就是别急着动代码,先花半天把版本关系理清楚。依赖是项目里最容易被忽略的资产,但它一旦出问题,代价往往是一个通宵。希望你现在手里的 pom,和这张对应表是能对上的。

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

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

立即咨询