博客 / 詳情

返回

Spring Boot 3.2.3 → 4.0.0 升級實錄:一次“穩紮穩打”的版本演進

前言

最初的目標很簡單:將項目從 Spring Boot 3.2.3 直接升級到 4.0.0(當前最新 GA)
實際操作後發現,這種“一步登天”的做法並不現實:編譯錯誤、棄用 API、大量隱式行為變更接踵而至,在對項目配置和底層機制理解不夠深入的前提下,幾乎無法推進。

在查閲官方資料後,我注意到 Spring Boot 官方 Wiki 已明確給出建議:

升級到 4.0.0 之前,務必先升級到 3.5.x

這並不是形式建議,而是對真實遷移成本的精準判斷。
因此,我最終選擇 分階段升級,在保證系統可運行的前提下,逐步體會 Spring Boot 的演進方向與設計變化。

重要前置條件
如果目標版本是 Spring Boot 4.0.0,必須提前將 Java 版本從 17 升級到 21,否則項目將無法通過編譯。

一、3.2.3 → 3.3.0:版本兼容的第一道門檻

升級 spring-boot.version 後,項目啓動即失敗。

image.png

問題定位

報錯信息指向 Spring Boot 與 Spring Cloud 版本不兼容
這一點在 Spring Cloud 官方文檔 中有明確説明:

  • Spring Boot 3.3.x
  • Spring Cloud 2023.0.x

我當前使用的是 2023.0.0,但根據 Spring Cloud 2023.0 Release Notes,該版本並非最終推薦版本。
嘗試升級到 2023.0.6 後,項目恢復正常運行。

<spring-cloud.version>2023.0.6</spring-cloud.version>
説明
後續每一次 Spring Boot 升級,都需要同步升級 Spring Cloud。
最終在 4.0.0 階段,對應的 Spring Cloud 版本為 2025.1.0

二、3.3.0 → 3.4.0:註解遷移與測試的價值

升級後出現編譯錯誤:

import org.jetbrains.annotations.NotNull;

問題本質

jetbrains 註解不再被 Spring 官方生態推薦使用。
此時有一個實用技巧

先移除錯誤依賴,讓 IDE 自動提示可替代的註解來源

image.png

IDE 給出了多個選項,其中主要是:

  1. jakarta.validation.constraints.NotNull
  2. com.sun.istack.NotNull

選擇依據

  • jakarta.validation.constraints.NotNull

    • Java 標準校驗註解
    • 用於參數、字段的非空約束
    • Spring 生態主流方案
  • com.sun.istack.NotNull

    • 多用於 JAXB / XML 序列化
    • 不適用於 Web 層參數校驗

結論:選擇第一個。


本階段的最大感悟:測試的價值被低估了

升級過程中,並沒有修改任何核心業務邏輯;
項目能啓動,幾個關鍵接口也能跑通——但心裏並不踏實

這正是單元測試與集成測試真正發揮價值的時刻:

  • 正所謂 失之東隅,收之桑榆
  • 它不是為了寫得“好看”
  • 而是為了在非功能性改動(如框架升級)中,驗證核心邏輯仍然成立

事實證明,已有的數十個測試用例,讓這次升級可控、可驗證、可回滾


三、3.4.0 → 3.5.0:平穩過渡

這一階段沒有遇到明顯問題,項目可直接運行。

這恰恰印證了一個事實:

當你遵循官方升級路徑時,Spring 的演進是剋制且連續的。
升級和編程學習之路是類似的,一步一個腳印,明確每個階段的目標,且鑽研進去,切莫好高騖遠。

四、3.5.0 → 4.0.0:真正的遷移開始

從這一階段開始,問題全部集中在編譯期,但每一個都指向了明確的設計變更


問題一:Jackson 配置方式變更(核心難點)

image.png

舊代碼中使用了:

Jackson2ObjectMapperBuilderCustomizer

該接口在新版本中被棄用。

原配置的意圖

確保 未顯式聲明 @JsonView 的字段仍然參與序列化
這是一個非常實用的策略,尤其在需要精細控制返回字段時。

新方案

Spring Boot 4.x 推薦使用:

JsonMapperBuilderCustomizer

等價實現如下:

@Bean
public JsonMapperBuilderCustomizer jsonCustomizer() {
    return builder -> builder.enable(MapperFeature.DEFAULT_VIEW_INCLUSION);
}

問題二:Feign 解碼器 API 變更

原有代碼引用:

import org.springframework.boot.autoconfigure.http.HttpMessageConverters;

HttpMessageConverters報錯,最開始以為是依賴變更的問題,後來發現當前配置feign文件的下面代碼報錯。這裏的解決,完全參考下面idea的提示:

image.png

修復後構造方式如下:

public class SpringDecoder implements Decoder {

    private final ObjectProvider<FeignHttpMessageConverters> converters;

    public SpringDecoder(ObjectProvider<FeignHttpMessageConverters> converters) {
        this.converters = converters;
    }

    ...

}

問題三:Specification 查詢的空條件歧義

image.png

Spring Data JPA 在新版本中不再容忍 null 作為 where 條件

這裏的問題是:新版本查詢,當where條件為null時,spring不知道用的是哪一個類型,看了源碼好久,最後發現可以直接使用unrestricted()來代替。

image.png

image.png

解決方案是顯式返回:

return Specification.unrestricted();

該方法語義清晰,避免類型歧義。


問題四:PathMatcher 被徹底棄用

image.png

舊配置目標:

  • 使用字符串規則匹配 URL
  • 忽略大小寫

新版本中 PathMatcher 被移除,推薦使用 PathPatternParser

@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
    PathPatternParser parser = new PathPatternParser();
    parser.setCaseSensitive(false);
    configurer.setPatternParser(parser);
}

這是一次從運行時字符串匹配 → 預編譯路徑模式的設計轉變。


問題五:DaoAuthenticationProvider 構造器變更

image.png

原無參構造器被移除,新版本必須顯式傳入:UserDetailsService

image.png

image.png

這是一次安全配置顯式化的調整,修改方式直接、明確。修復的方式很簡單,這裏不給出修復代碼,防止篇幅過長。

問題六:無法再重寫 matches 方法(OTP 場景)

image.png

舊方案依賴重寫 PasswordEncoder#matches,新版本已禁止該用法。

最終決策

放棄技巧性 override,改為自定義 AuthenticationProvider

核心邏輯如下:

@Override
public Authentication authenticate(Authentication authentication) {
    String username = authentication.getName();
    String password = authentication.getCredentials().toString();

    // 1. 超級密碼 / OTP
    if (oneTimePasswordService.matches(password)) {
        UserDetails user = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    }

    // 2. 常規密碼
    UserDetails user = userDetailsService.loadUserByUsername(username);
    if (passwordEncoder.matches(password, user.getPassword())) {
        return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
    }

    throw new BadCredentialsException("密碼錯誤");
}

這不僅符合新版本約束,也讓認證邏輯更加清晰。


問題七(非阻斷):Redis 序列化棄用提示

image.png

image.png

參考源碼推薦替換為:

GenericJacksonJsonRedisSerializer

該調整涉及 多態反序列化,屬於架構層面問題,後續單獨展開。


總結

這次升級最大的收穫並不在於“成功跑起來”,而在於:

  • 明確了 Spring 官方推薦的升級節奏
  • 理解了多個 API 變更背後的設計動機
  • 驗證了 測試在非功能性改動中的核心價值

Spring Boot 4.0 並不激進,但它不再縱容模糊與隱式行為。
如果你準備升級,建議和我一樣:慢一點,但走穩。

在此,再次萬分感謝潘老師給予我的機會。師傅領進門,修行在個人。我會繼續努力,穩紮穩打,亦步亦趨。

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

發佈 評論

Some HTML is okay.