前言
最初的目標很簡單:將項目從 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 後,項目啓動即失敗。
問題定位
報錯信息指向 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 自動提示可替代的註解來源
IDE 給出了多個選項,其中主要是:
jakarta.validation.constraints.NotNullcom.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 配置方式變更(核心難點)
舊代碼中使用了:
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的提示:
修復後構造方式如下:
public class SpringDecoder implements Decoder {
private final ObjectProvider<FeignHttpMessageConverters> converters;
public SpringDecoder(ObjectProvider<FeignHttpMessageConverters> converters) {
this.converters = converters;
}
...
}
問題三:Specification 查詢的空條件歧義
Spring Data JPA 在新版本中不再容忍 null 作為 where 條件。
這裏的問題是:新版本查詢,當where條件為null時,spring不知道用的是哪一個類型,看了源碼好久,最後發現可以直接使用unrestricted()來代替。
解決方案是顯式返回:
return Specification.unrestricted();
該方法語義清晰,避免類型歧義。
問題四:PathMatcher 被徹底棄用
舊配置目標:
- 使用字符串規則匹配 URL
- 忽略大小寫
新版本中 PathMatcher 被移除,推薦使用 PathPatternParser:
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
PathPatternParser parser = new PathPatternParser();
parser.setCaseSensitive(false);
configurer.setPatternParser(parser);
}
這是一次從運行時字符串匹配 → 預編譯路徑模式的設計轉變。
問題五:DaoAuthenticationProvider 構造器變更
原無參構造器被移除,新版本必須顯式傳入:UserDetailsService。
這是一次安全配置顯式化的調整,修改方式直接、明確。修復的方式很簡單,這裏不給出修復代碼,防止篇幅過長。
問題六:無法再重寫 matches 方法(OTP 場景)
舊方案依賴重寫 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 序列化棄用提示
參考源碼推薦替換為:
GenericJacksonJsonRedisSerializer
該調整涉及 多態反序列化,屬於架構層面問題,後續單獨展開。
總結
這次升級最大的收穫並不在於“成功跑起來”,而在於:
- 明確了 Spring 官方推薦的升級節奏
- 理解了多個 API 變更背後的設計動機
- 驗證了 測試在非功能性改動中的核心價值
Spring Boot 4.0 並不激進,但它不再縱容模糊與隱式行為。
如果你準備升級,建議和我一樣:慢一點,但走穩。
在此,再次萬分感謝潘老師給予我的機會。師傅領進門,修行在個人。我會繼續努力,穩紮穩打,亦步亦趨。