从 spring boot 2.x 到 3.5.x + jdk21:一次完整的生产环境迁移实战
升级背景
在私有化部署过程中,客户使用安全扫描工具检测到大量安全漏洞,主要集中在:
- 框架版本过低:spring boot 2.1.6.release(发布于 2019 年)
- jdk 版本过旧:jdk 8(缺乏最新安全补丁)
- 第三方依赖:多个依赖存在已知 cve 漏洞
基于安全合规和长期维护的考虑,决定进行大版本升级。
- 当前版本:spring boot 2.1.6.release + jdk 8
- 目标版本:spring boot 3.5.4 + jdk 21 lts
升级目标与核心变化
主要变化
| 类别 | 变化内容 | 迁移方式 |
|---|---|---|
| 命名空间 | javax.* → jakarta.* | 自动化迁移 |
| jdk 版本 | java 8 → java 21 lts | 自动化迁移 + 手动调整 |
| 第三方依赖 | 大量依赖需要升级 | 手动处理 |
| api 文档 | swagger 2.x → springdoc openapi 3.x | 配置调整 |
| 安全配置 | websecurityconfigureradapter 废弃 | 重写配置类 |
为什么选择自动化迁移?
前两项(命名空间和 jdk 版本)涉及的代码改动量极大,手动修改容易出错且效率低下。
openrewrite 作为业界成熟的自动化重构工具,可以完成大部分繁琐工作。
完整升级步骤
第一阶段:准备工作(jdk 8 环境)
代码分支管理
# 确保主分支代码为最新 git checkout dev git pull origin dev # 创建升级专用分支 git checkout -b upgrade/springboot3-jdk21
引入 openrewrite maven 插件
什么是 openrewrite?
openrewrite 是一个自动化代码重构和迁移工具,专为 java 生态系统设计。
核心优势:
- 精确安全:在 ast(抽象语法树)层面操作,不会破坏代码结构
- 批量处理:一次性处理整个代码库
- 可预览:使用
rewrite:dryrun查看变更预览 - 可定制:支持声明式(yaml)或编程式自定义规则
工作原理:
openrewrite 通过解析源代码生成无损语法树(lst),在 ast 层面进行精确转换,完整保留:
- 原始格式和缩进
- 所有注释
- 代码风格
配置方式:
在 pom.xml 的 <plugins> 节点下添加:
<plugin>
<groupid>org.openrewrite.maven</groupid>
<artifactid>rewrite-maven-plugin</artifactid>
<version>6.15.0</version>
<configuration>
<exportdatatables>true</exportdatatables>
<activerecipes>
<!-- 升级到 java 21 -->
<recipe>org.openrewrite.java.migrate.upgradetojava21</recipe>
<!-- junit 4 to 5 -->
<recipe>org.openrewrite.java.spring.boot2.springboot2junit4to5migration</recipe>
<!-- spring boot 3.4(插件暂不支持 3.5,升级后手动改) -->
<recipe>org.openrewrite.java.spring.boot3.upgradespringboot_3_4</recipe>
</activerecipes>
</configuration>
<dependencies>
<dependency>
<groupid>org.openrewrite.recipe</groupid>
<artifactid>rewrite-migrate-java</artifactid>
<version>3.14.1</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupid>org.openrewrite.recipe</groupid>
<artifactid>rewrite-spring</artifactid>
<version>6.11.1</version>
</dependency>
</dependencies>
</plugin>配方(recipe)说明:
upgradespringboot_3_4 :升级至 spring boot 3.4.x(插件暂不支持 3.5,升级后手动修改版本号即可)upgradetojava21 :升级至 jdk 21(spring boot 配方仅升级到 jdk 17,需额外添加此配方)springboot2junit4to5migration :升级测试框架,避免自动化测试报错
提示:你也可以编写自定义配方来处理项目特定的迁移需求。
执行自动化迁移
mvn rewrite:run
或者在 idea 中通过 maven 面板执行:

执行时间: 几分钟到几十分钟不等,取决于项目规模。
可能遇到的问题:
- 如果某些类包含特殊代码导致报错,可以先注释掉,待升级完成后再处理
- 执行完成后可以删除该插件(也可以保留,以便后续增量升级)
openrewrite 自动完成的变更
执行完成后,主要变化包括:
依赖升级:
pom.xml中的依赖版本自动升级- spring boot 版本升级到 3.4.x(手动改为 3.5.4)
包名变更:
javax.servlet.* → jakarta.servlet.*javax.persistence.* → jakarta.persistence.*javax.validation.* → jakarta.validation.*
api 文档迁移:
- swagger 2.x → springdoc openapi 3.x
jdk 新特性应用:
- text blocks:多行字符串的优雅处理
// 自动转换为
string json = """
{
"name": "user",
"age": 18
}
""";- instanceof 模式匹配:简化类型判断和转换
if (obj instanceof string s) {
system.out.println(s.touppercase());
}- string.formatted(): 替代
string.format()
"hello, %s!".formatted(name);
- 集合增强: getfirst() 替代 get(0)
- @serial 注解:标记序列化相关字段
第三方库升级:
- apache httpclient
- apache commons 系列
- 其他常用工具库
增量合并场景处理
场景: 执行 rewrite 后,旧分支又有代码提交,合并时出现大量 javax 包名和 swagger 注解冲突。
解决方案:使用 intellij idea 自带的 refactor 功能(本质也是基于 openrewrite)
操作步骤:
- 打开 idea,选择
refactor → migrate packages and classes - 选择迁移规则(javax → jakarta)
- 预览变更并执行


第二阶段:环境切换(jdk 21 环境)
重要分界线:以下操作需在 jdk 21 环境下进行。
6. 修改 idea 项目配置
修改 sdk 和 language level(快捷键:ctrl + alt + shift + s):

修改 modules 的 language level:

修改 java compiler(快捷键:ctrl + alt + s):

核心问题与解决方案
问题一:hibernate ddl auto 的陷阱
严重警告:在完成以下配置前,切勿启动项目!否则可能导致数据库结构被错误修改。
问题背景
新旧版本 hibernate 的行为差异:

为什么要禁用?
在生产环境中使用 spring.jpa.hibernate.ddl-auto=update 存在严重风险:
- 数据安全风险:自动更新可能导致意外的数据丢失或结构变更
- 性能问题:启动时全表检查会显著增加应用启动时间
- 版本控制缺失:无法追踪数据库变更历史,不利于团队协作和回滚
- 升级后风险更高:hibernate 6.x 的校验更严格,误操作概率增加
解决方案
方案一:配置优先级控制(推荐)
在 ci/cd 启动脚本中设置 vm 参数:
java -jar app.jar -dspring.jpa.hibernate.ddl-auto=none
优先级:vm 参数 > 配置中心(apollo/nacos) > application.properties
方案二:使用专业的数据库版本管理工具
推荐使用 flyway 或 liquibase 管理数据库脚本:
<dependency> <groupid>org.flywaydb</groupid> <artifactid>flyway-core</artifactid> </dependency>
方案三:结构对比工具
- navicat:提供结构同步功能
- datagrip:intellij 系产品,支持数据库结构对比
问题二:spring security 配置迁移
核心变化
websecurityconfigureradapter已废弃- 推荐使用 lambda dsl 配置方式
- 配置方式从继承改为 bean 注册
迁移示例
旧版配置(spring security 5.x):
@configuration
@enablewebsecurity
public class securityconfig extends websecurityconfigureradapter {
@override
protected void configure(httpsecurity http) throws exception {
http
.csrf().disable()
.sessionmanagement()
.sessioncreationpolicy(sessioncreationpolicy.stateless)
.and()
.authorizerequests()
.antmatchers("/api/public/**").permitall()
.anyrequest().authenticated();
}
}新版配置(spring security 6.x):
@configuration
@enablewebsecurity
public class securityconfig {
private final tokenprovider tokenprovider;
public securityconfig(tokenprovider tokenprovider) {
this.tokenprovider = tokenprovider;
}
@bean
public securityfilterchain filterchain(httpsecurity http) throws exception {
http
.csrf(abstracthttpconfigurer::disable)
.sessionmanagement(sessionmanagement -> sessionmanagement
.sessioncreationpolicy(sessioncreationpolicy.stateless))
.authorizehttprequests(authorizerequests -> authorizerequests
// 允许所有 options 请求
.requestmatchers(options, "**").permitall()
.requestmatchers(
"/swagger-ui/**",
"/v3/api-docs/**",
"/swagger-resources/**",
"/images/**",
"/webjars/**").permitall()
.anyrequest().authenticated())
.addfilterbefore(new jwtfilter(tokenprovider), usernamepasswordauthenticationfilter.class);
return http.build();
}
}requestmatcher 调整注意事项
- 新增 springdoc 路径(必须)
/swagger-ui/** /v3/api-docs/**
修正通配符写法:
❌ 错误: //**/*.js ✅ 正确: /**/*.js 否则会抛出 patternparseexception
问题三:springdoc openapi 配置
swagger → springdoc 迁移
<!-- 移除旧的 swagger 依赖 -->
<!--
<dependency>
<groupid>io.springfox</groupid>
<artifactid>springfox-swagger2</artifactid>
</dependency>
-->
<!-- 添加新的 springdoc 依赖 -->
<dependency>
<groupid>org.springdoc</groupid>
<artifactid>springdoc-openapi-starter-webmvc-ui</artifactid>
<version>2.3.0</version>
</dependency>配置示例
@configuration
@openapidefinition
public class swaggerconfig {
@bean
public openapi openapi() {
openapi openapi = new openapi();
openapi.info(new info().title("api 文档").version("1.0"));
// 配置 authorization 登录鉴权
map<string, securityscheme> map = map.of("authorization",
new securityscheme()
.type(securityscheme.type.apikey)
.in(securityscheme.in.header)
.name("authorization"));
openapi.components(new components().securityschemes(map));
map.keyset().foreach(key -> openapi.addsecurityitem(new securityrequirement().addlist(key)));
return openapi;
}
}注解对应关系
| swagger 2.x | springdoc openapi 3.x |
|---|---|
| @api | @tag |
| @apioperation | @operation |
| @apiparam | @parameter |
| @apimodel | @schema |
| @apimodelproperty | @schema |
访问地址变更
原 swagger ui 地址: http://localhost:8080/swagger-ui.html
新 springdoc 地址: http://localhost:8080/swagger-ui/index.html

问题四:依赖冲突与安全漏洞修复
检测工具
使用 idea 自带的依赖分析工具:

必须升级的依赖(存在高危漏洞)
推荐使用 owasp dependency-check 或 snyk 扫描:
mvn dependency-check:check
解决依赖冲突的技巧
问题:maven 依赖解析采用"最短路径优先"和"第一声明优先"原则,可能导致旧版本覆盖新版本。
解决方案:显式声明期望的版本
<dependencies>
<!-- 显式声明 spring framework 版本,避免被传递依赖覆盖 -->
<dependency>
<groupid>org.springframework</groupid>
<artifactid>spring-core</artifactid>
<version>6.1.3</version>
</dependency>
</dependencies>快速检测技巧:
在 idea 的 maven 依赖树中搜索 release ,spring 新版本已不使用 release 后缀,搜索到的基本都是旧版本。

问题五:url 尾斜杠匹配策略变更
行为变化
| 版本 | 行为 |
|---|---|
| spring boot 2.x | /api/user/get 和 /api/user/get/ 视为同一接口 |
| spring boot 3.x | /api/user/get 和 /api/user/get/ 视为不同接口 |
常见导致尾斜杠的情况
- case 1:类注解带尾斜杠
@requestmapping("/api/user/")
public class usercontroller {
@postmapping("login") // 实际路径:/api/user/login
}
case 2:空字符串映射
@requestmapping("/api/user")
public class usercontroller {
@postmapping("") // 实际路径:/api/user/(带尾斜杠!)
}
case 3:根路径映射
@postmapping("/") // 实际路径:/(带尾斜杠)
**** 检查方式
- idea endpoints 工具窗口:查看所有端点
- springdoc ui:访问 swagger 页面检查

临时解决方案(不推荐长期使用)
import org.springframework.context.annotation.configuration;
import org.springframework.web.servlet.config.annotation.pathmatchconfigurer;
import org.springframework.web.servlet.config.annotation.webmvcconfigurer;
@configuration
public class webconfiguration implements webmvcconfigurer {
@override
public void configurepathmatch(pathmatchconfigurer configurer) {
// 设 置 为 true 以 忽 略 尾 斜 杠 , 恢 复 旧 版 本 行 为
configurer.setusetrailingslashmatch(true);
}
}注意__: ·
setusetrailingslashmatch在 spring 6.x 后已标记为废弃,后续版本将删除。建议逐步修正所有端点,去除尾斜杠。
根本解决方案
- 修正所有 controller 的路径映射
- 通知前端团队同步修改调用路径
- 如果有硬编码的 url,全局搜索并修正
- 使用测试确保前后端调用正常
问题六:apache poi / easyexcel 升级
背景
apache poi 旧版本(< 5.0)存在多个 cve 安全漏洞,必须升级。
推荐方案
对于新项目:直接使用 fastexcel
<dependency>
<groupid>cn.idev.excel</groupid>
<artifactid>fastexcel</artifactid>
<version>1.0.0</version>
</dependency>对于使用 easyexcel 的旧项目:
<dependency>
<groupid>com.alibaba</groupid>
<artifactid>easyexcel</artifactid>
<version>4.0.3</version>
</dependency>说明:easyexcel 已不再维护,fastexcel 是社区维护的替代方案,api 基本兼容。
迁移注意事项
easyexcel 跨大版本升级(2.x → 4.x)api 变化较大,主要改动:
1. 监听器接口方法签名调整
2. 部分工具类包路径变更
3. 自定义转换器需要适配新接口
建议参考官方迁移文档:easyexcel官方文档 - 基于java的excel处理工具 | easy excel 官网
问题七:jdk 模块化限制(–add-opens)
问题现象
某些依赖库使用反射访问 jdk 内部 api,在 jdk 9+ 模块化系统下会报错:
inaccessibleobjectexception: unable to make field accessible: module java.base does not "opens java.net" to unnamed module
解决方案
在 idea 运行配置中添加 vm 参数:
开启 vm 参数配置(默认隐藏):

解决方案
在 idea 运行配置中添加 vm 参数:
开启 vm 参数配置(默认隐藏):

常见需要开放的模块
--add-opens java.base/java.lang=all-unnamed --add-opens java.base/java.util=all-unnamed --add-opens java.base/java.lang.reflect=all-unnamed --add-opens java.base/sun.nio.ch=all-unnamed
问题八:过期配置属性警告
问题现象
启动时出现警告:
property 'spring.xxx.yyy' is deprecated

解决方案:
- 查看 spring boot 官方迁移文档
- 使用 idea 的智能提示查看替代属性
- 修改配置文件( 常见过期属性:
application.yml或配置中心)
常见过期属性:
| 过期属性 | 替代属性 |
|---|---|
| spring.datasource.type | 自动推断,无需配置 |
| spring.jpa.properties.hibernate.temp.use_jdbc_metadata_defaults | 已移除 |
| management.metrics.export.prometheus.enabled | management.prometheus.metrics.export.enabled |
完整测试清单
升级完成后,务必进行全面的回归测试:
- spring security:认证、授权是否正常
- springdoc:api 文档是否可访问( /swagger-ui/index.html )
- 数据库操作:jpa/mybatis 是否正常工作
- 缓存:redis/caffeine 等缓存是否生效
- 消息队列:rabbitmq/kafka 等是否正常
- 定时任务:scheduled/quartz 是否按预期执行
- 文件上传/下载:文件 io 操作是否正常
- 业务功能:核心业务流程是否正常(重点关注有代码改动的地方)
- 性能测试:对比升级前后的性能指标
升级感悟
框架层面的变化趋势
通过这次升级,我观察到现代框架的一些发展趋势:
- 校验更严格:
- spring 不再容忍 url 尾斜杠的模糊匹配
- 循环依赖检测更严格(默认禁止)
- hibernate 对实体状态的校验更精确
- 安全性优先:
- 默认配置更保守
- 废弃不安全的 api
- 强制升级修复已知漏洞
- 现代化 api:
- lambda dsl 配置风格
- 函数式编程支持
- 更简洁的 api 设计
依赖选择建议
基于这次升级经验,对于第三方库的选择建议:
优先选择:
✅ 国际主流项目(apache、spring 生态等)
✅ 有完善文档和测试的项目
✅ 活跃维护且社区规模大的项目
✅ 语义化版本管理清晰的项目
谨慎选择:
⚠️ 缺乏自动化测试的项目
⚠️ 长期未更新的项目
⚠️ api 设计不稳定、频繁 breaking change 的项目
⚠️ 文档不全、维护团队不稳定的项目
自动化迁移的价值
openrewrite 等自动化工具在大版本升级中的价值无可替代:
- 减少 90% 以上的机械性改动
- 避免手工替换导致的遗漏
- 保持代码风格和注释
- 降低升级风险
建议在日常开发中也关注此类工具,提升团队整体效率。
本文首发于 [尚硅谷编程联盟],转载请注明出处。
最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
到此这篇关于spring boot3 + jdk21 的迁移超详细步骤(最新推荐)的文章就介绍到这了,更多相关springboot jdk迁移内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论