从 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生产环境迁移实战
@
目录
- 从 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生产环境迁移实战
- 升级背景
- 升级目标与核心变化
- 完整升级步骤
- 第一阶段:准备工作(JDK 8 环境)
- 代码分支管理
- 引入 OpenRewrite Maven 插件
- 执行自动化迁移
- OpenRewrite 自动完成的变更
- 增量合并场景处理
- 第二阶段:环境切换(JDK 21 环境)
- 核心问题与解决方案
- 问题一:Hibernate DDL Auto 的陷阱
- 问题二:Spring Security 配置迁移
- 问题三:SpringDoc OpenAPI 配置
- 问题四:依赖冲突与安全漏洞修复
- 问题五:URL 尾斜杠匹配策略变更
- 问题六:Apache POI / EasyExcel 升级
- 问题七:JDK 模块化限制(--add-opens)
- 问题八:过期配置属性警告
- 完整测试清单
- 升级感悟
- 最后:
升级背景
在私有化部署过程中,客户使用安全扫描工具检测到大量安全漏洞,主要集中在:
- 框架版本过低: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 的 节点下添加:- <plugin>
- <groupId>org.openrewrite.maven</groupId>
- rewrite-maven-plugin</artifactId>
- <version>6.15.0</version>
- <configuration>
- <exportDatatables>true</exportDatatables>
-
-
- <recipe>org.openrewrite.java.migrate.UpgradeToJava21</recipe>
-
- <recipe>org.openrewrite.java.spring.boot2.SpringBoot2JUnit4to5Migration</recipe>
-
- <recipe>org.openrewrite.java.spring.boot3.UpgradeSpringBoot_3_4</recipe>
- </activeRecipes>
- </configuration>
- <dependencies>
- <dependency>
- <groupId>org.openrewrite.recipe</groupId>
- rewrite-migrate-java</artifactId>
- <version>3.14.1</version>
- <scope>runtime</scope>
- </dependency>
- <dependency>
- <groupId>org.openrewrite.recipe</groupId>
- 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 :升级测试框架,避免自动化测试报错
提示:你也可以编写自定义配方来处理项目特定的迁移需求。
执行自动化迁移
或者在 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 新特性应用:
- // 自动转换为
- 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>
- 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 调整注意事项
- /swagger-ui/**
- /v3/api-docs/**
复制代码 修正通配符写法:- ❌ 错误: //**/*.js
- ✅ 正确: /**/*.js
- 否则会抛出 PatternParseException
复制代码 问题三:SpringDoc OpenAPI 配置
Swagger → SpringDoc 迁移-
-
-
- <dependency>
- <groupId>org.springdoc</groupId>
- 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.xSpringDoc 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>
-
- <dependency>
- <groupId>org.springframework</groupId>
- 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/ 视为不同接口常见导致尾斜杠的情况
- @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>
- fastexcel</artifactId>
- <version>1.0.0</version>
- </dependency>
复制代码 对于使用 EasyExcel 的旧项目:- <dependency>
- <groupId>com.alibaba</groupId>
- 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.enabledmanagement.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
- 强制升级修复已知漏洞
- Lambda DSL 配置风格
- 函数式编程支持
- 更简洁的 API 设计
依赖选择建议
基于这次升级经验,对于第三方库的选择建议:
优先选择:
✅ 国际主流项目(Apache、Spring 生态等)
✅ 有完善文档和测试的项目
✅ 活跃维护且社区规模大的项目
✅ 语义化版本管理清晰的项目
谨慎选择:
⚠️ 缺乏自动化测试的项目
⚠️ 长期未更新的项目
⚠️ API 设计不稳定、频繁 Breaking Change 的项目
⚠️ 文档不全、维护团队不稳定的项目
自动化迁移的价值
OpenRewrite 等自动化工具在大版本升级中的价值无可替代:
- 减少 90% 以上的机械性改动
- 避免手工替换导致的遗漏
- 保持代码风格和注释
- 降低升级风险
建议在日常开发中也关注此类工具,提升团队整体效率。
本文首发于 [尚硅谷编程联盟],转载请注明出处。
最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |