博客 / 詳情

返回

從 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生產環境遷移實戰

從 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生產環境遷移實戰

@

目錄
  • 從 Spring Boot 2.x 到 3.5.x + JDK21:一次完整的生產環境遷移實戰
  • 升級背景
  • 升級目標與核心變化
  • 完整升級步驟
    • 第一階段:準備工作(JDK 8 環境)
      • 代碼分支管理
      • 引入 OpenRewrite Maven 插件
      • 執行自動化遷移
      • OpenRewrite 自動完成的變更
      • 增量合併場景處理
    • 第二階段:環境切換(JDK 21 環境)
      • 6. 修改 IDEA 項目配置
  • 核心問題與解決方案
    • 問題一: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 生態系統設計。

核心優勢

  1. 精確安全:在 AST(抽象語法樹)層面操作,不會破壞代碼結構
  2. 批量處理:一次性處理整個代碼庫
  3. 可預覽:使用 rewrite:dryRun 查看變更預覽
  4. 可定製:支持聲明式(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 面板執行:

執行時間: 幾分鐘到幾十分鐘不等,取決於項目規模。

可能遇到的問題:

  1. 如果某些類包含特殊代碼導致報錯,可以先註釋掉,待升級完成後再處理
  2. 執行完成後可以刪除該插件(也可以保留,以便後續增量升級)

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)

操作步驟:

  1. 打開 IDEA,選擇 Refactor → Migrate Packages and Classes
  2. 選擇遷移規則(javax → jakarta)
  3. 預覽變更並執行

第二階段:環境切換(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 存在嚴重風險:

  1. 數據安全風險:自動更新可能導致意外的數據丟失或結構變更
  2. 性能問題:啓動時全表檢查會顯著增加應用啓動時間
  3. 版本控制缺失:無法追蹤數據庫變更歷史,不利於團隊協作和回滾
  4. 升級後風險更高:Hibernate 6.x 的校驗更嚴格,誤操作概率增加

解決方案

方案一:配置優先級控制(推薦)

在 CI/CD 啓動腳本中設置 VM 參數:

java -jar app.jar -Dspring.jpa.hibernate.ddl-auto=none

優先級:VM 參數 > 配置中心(Apollo/Nacos) > application.properties

方案二:使用專業的數據庫版本管理工具

推薦使用 FlywayLiquibase 管理數據庫腳本:

<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 調整注意事項

  1. 新增 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-CheckSnyk 掃描:

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("/") // 實際路徑:/(帶尾斜槓)


**** 檢查方式

  1. IDEA Endpoints 工具窗口:查看所有端點
  2. 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 後已標記為廢棄,後續版本將刪除。建議逐步修正所有端點,去除尾斜槓。

根本解決方案

  1. 修正所有 Controller 的路徑映射
  2. 通知前端團隊同步修改調用路徑
  3. 如果有硬編碼的 URL,全局搜索並修正
  4. 使用測試確保前後端調用正常

問題六: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

解決方案:

  1. 查看 Spring Boot 官方遷移文檔

  2. 使用 IDEA 的智能提示查看替代屬性

  3. 修改配置文件( 常見過期屬性: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 操作是否正常
  • 業務功能:核心業務流程是否正常(重點關注有代碼改動的地方)
  • 性能測試:對比升級前後的性能指標

升級感悟

框架層面的變化趨勢

通過這次升級,我觀察到現代框架的一些發展趨勢:

  1. 校驗更嚴格
  • Spring 不再容忍 URL 尾斜槓的模糊匹配
  • 循環依賴檢測更嚴格(默認禁止)
  • Hibernate 對實體狀態的校驗更精確
  1. 安全性優先
  • 默認配置更保守
  • 廢棄不安全的 API
  • 強制升級修復已知漏洞
  1. 現代化 API
  • Lambda DSL 配置風格
  • 函數式編程支持
  • 更簡潔的 API 設計

依賴選擇建議

基於這次升級經驗,對於第三方庫的選擇建議:

優先選擇

國際主流項目(Apache、Spring 生態等)

有完善文檔和測試的項目

活躍維護且社區規模大的項目

語義化版本管理清晰的項目

謹慎選擇

⚠️ 缺乏自動化測試的項目

⚠️ 長期未更新的項目

⚠️ API 設計不穩定、頻繁 Breaking Change 的項目

⚠️ 文檔不全、維護團隊不穩定的項目

自動化遷移的價值

OpenRewrite 等自動化工具在大版本升級中的價值無可替代:

  • 減少 90% 以上的機械性改動
  • 避免手工替換導致的遺漏
  • 保持代碼風格和註釋
  • 降低升級風險

建議在日常開發中也關注此類工具,提升團隊整體效率。

本文首發於 [尚硅谷編程聯盟],轉載請註明出處。

最後:

“在這個最後的篇章中,我要表達我對每一位讀者的感激之情。你們的關注和回覆是我創作的動力源泉,我從你們身上吸取了無盡的靈感與勇氣。我會將你們的鼓勵留在心底,繼續在其他的領域奮鬥。感謝你們,我們總會在某個時刻再次相遇。”

在這裏插入圖片描述

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.