跳至主要内容

Spring Boot 4.0 升級紀錄

本文檔記錄 appfuse-server 和 app-server 從 Spring Boot 3.5.x 升級到 4.0.x 的完整過程, 同時作為升級計劃、進度追蹤和過程紀錄。

第二次升級:基於第一次升級的經驗,採用改進後的方法論重新執行。


升級概覽

項目升級前升級後
JDK24 (Temurin-24.0.1)25
Spring Boot3.5.64.0.1
Spring Security6.x7.x
Hibernate6.x7.x
Jackson2.x (com.fasterxml.jackson)3.x (tools.jackson)
Tomcat10.x11.x

JDK 版本管理

使用 sdkman 管理 JDK 版本:

# 查看可用的 JDK 25 版本
sdk list java | grep 25

# 安裝 JDK 25 (Temurin)
sdk install java 25.ea.xx-tem # 或正式版號

# 切換到 JDK 25
sdk use java 25.ea.xx-tem

# 設為預設
sdk default java 25.ea.xx-tem

升級策略

採用「從乾淨框架逐步移植」策略,詳見 主版本升級方法論

本次改進重點

  1. Phase 0 完整盤點(模組、resources、META-INF)
  2. Phase 3 逐模組移植(不一次複製所有)
  3. 每個批次都執行測試
  4. 區分 Breaking Change vs Deprecation

事前準備

appfuse-server 模組清單(30 個)

批次模組說明預期 Breaking Changes
1env/環境配置EnvironmentPostProcessor deprecated
1bean/Bean 工具PropertyMap 需 @JsonCreator
1converter/類型轉換器-
2json/JSON 處理Jackson 3 套件變更
2xml/XML 處理-
2csv/CSV 處理Jackson 3 套件變更
3auth/認證工具AbstractAuthenticationToken
3oauth2/OAuth2 工具-
3security/安全工具-
4entity/Entity 工具-
4jpa/JPA 工具-
4search/RSQL 搜尋-
4cache/快取-
5file/檔案存儲-
5content/內容檢測-
5image/圖片處理-
5mail/郵件-
5web/Web 工具-
5docs/文檔工具-
5http/HTTP 客戶端-
5exception/異常處理-
5error/錯誤處理-
5measure/度量單位-
5nls/國際化-
5record/記錄工具-
5serial/序列化-
5ehcache/Ehcache 配置-
5font/字體-
5helper/工具-
5resource/資源-

appfuse-server resources 清單

src/main/resources/
├── META-INF/
│ └── spring.factories ← 重要!EnvironmentPostProcessor 註冊
├── io/ ← 可能是 native hints
├── static/
└── templates/

META-INF/spring.factories 內容

org.springframework.boot.env.EnvironmentPostProcessor=\
io.leandev.appfuse.env.EnvironConfigInjector

org.springframework.context.ApplicationListener=\
io.leandev.appfuse.env.EnvironInitializer

appfuse-server 依賴清單

api scope(傳遞給消費者)

依賴Spring Boot 4.x 變更
starter-actuator-
starter-data-jpa-
starter-hateoas-
starter-mail-
starter-quartz-
starter-security-
starter-oauth2-resource-serverstarter-security-oauth2-resource-server
starter-validation-
starter-webstarter-webmvc
ehcache-
httpclient5-
jjwt-api (0.12.5)-

implementation scope

依賴說明
kryoJava 序列化
dom4jXML 處理
jaxenXPath
rsql-parserRSQL 搜尋解析
jackson-dataformat-csv→ tools.jackson
twelvemonkeys-imageio圖片格式支援
batik-transcoderSVG 處理

runtimeOnly scope

依賴說明
jjwt-implJJWT 實作
jjwt-jacksonJJWT Jackson

compileOnly scope(由應用層引入)

依賴說明
lombok-
aws-sdk:s3S3/MinIO
azure-storage-blobAzure Blob
sshd-sftpSFTP

app-server 模組清單(17 個)

模組說明預期 Breaking Changes
actuator/自訂 ActuatorHealthIndicator 套件變更
config/配置類別H2Console PathRequest 移除
controller/REST Controller-
converter/轉換器-
dto/DTO-
entity/JPA Entity-
exception/異常處理-
handler/Handler-
initializer/初始化-
listener/事件監聽-
mapper/Mapper-
repository/JPA Repository-
scheduler/排程-
security/安全配置-
service/服務層-
util/工具-

app-server resources 清單

src/main/resources/
├── META-INF/ ← 空目錄
├── application.yml
├── app-server.yml
├── application-mail-example.yaml
├── data/ ← 初始化資料 JSON
├── doc/ ← 測試用圖片
├── static/
└── templates/

app-server 額外依賴

依賴說明Spring Boot 4.x 變更
appfuse-server框架-
sshd-sftpSFTP-
aws-sdk:s3S3/MinIO-
azure-storage-blobAzure-
h2嵌入式資料庫-
mssql-jdbcSQL Server-
mysql-connector-jMySQL-
ojdbc11Oracle-
postgresqlPostgreSQL-
starter-tomcatWAR 部署starter-tomcat-runtime

預期 Breaking Changes

基於第一次升級經驗和官方 Migration Guide:

項目影響模組類型處理方式
starter-webstarter-webmvcbuild.gradleBreakingPhase 2 配置
starter-oauth2-resource-serverstarter-security-oauth2-resource-serverbuild.gradleBreakingPhase 2 配置
Jackson 2 → 3 套件名稱json/, csv/BreakingPhase 3 批次 2
AbstractAuthenticationToken 構造函數歧義auth/BreakingPhase 3 批次 3
HealthIndicator 套件位置消費者BreakingPhase 5
H2Console PathRequest.toH2Console() 移除消費者BreakingPhase 5
測試 import 變更(DataJpaTest 等)所有測試BreakingPhase 3, 5
EnvironmentPostProcessor deprecatedenv/DeprecationPhase 7
JJWT vs Nimbusauth/評估Phase 7(決定保留 JJWT)

升級進度

Phase 0: 準備工作

  • 刪除舊的 app-server-sb4 和 appfuse-server-sb4
  • 建立升級紀錄文檔(本文檔)
  • 盤點 appfuse-server - [x] Java 模組清單(30 個模組) - [x] resources 目錄(含 META-INF/spring.factories) - [x] 依賴清單(api/implementation/runtimeOnly/compileOnly)
  • 盤點 app-server - [x] Java 模組清單(17 個模組) - [x] resources 目錄 - [x] 額外依賴
  • 研究官方 Migration Guide - [ ] Spring Boot 4.0 Migration Guide - [ ] Spring Security 7.0 Migration Guide - [ ] Hibernate 7.0 Migration Guide - [ ] Jackson 3.0 Migration Guide
  • 確認「預期 Breaking Changes 清單」完整(基於第一次升級經驗)

Phase 1: 升級 JDK 並產生 app-server-sb4

1.1 升級 JDK 25

  • 使用 sdkman 安裝 JDK 25 bash sdk list java | grep 25 sdk default java 25.0.1-tem java -version # 確認版本
  • 驗證 JDK 25 已啟用 - 版本: OpenJDK 25.0.1 (Temurin-25.0.1+8)

1.2 產生 app-server-sb4

  • 使用 Spring Initializr 產生專案(javaVersion=25)
  • 驗證可正常編譯和啟動 - 啟動時間: 2.053 秒 - Tomcat 11.0.15 - Hibernate ORM 7.2.0.Final
  • 記錄 starter 名稱差異(見下方「新發現的 Starter 變更」)

Phase 2: 建立 appfuse-server-sb4

  • 建立 java-library 專案結構
  • 配置 build.gradle.kts - Spring Boot 4.0.1 - starter-webmvc (取代 starter-web) - starter-security-oauth2-resource-server (取代 starter-oauth2-resource-server) - tools.jackson.dataformat:jackson-dataformat-csv (取代 com.fasterxml) - 測試 starter 拆分為 webmvc-test, data-jpa-test, security-test
  • 配置 Composite Build (app-server-sb4 引用 appfuse-server-sb4)
  • 驗證空專案可編譯 (BUILD SUCCESSFUL)

Phase 3: 逐模組移植 appfuse-server

批次 1: 基礎工具

  • env/ - 環境配置(EnvironmentPostProcessor deprecated warning)
  • bean/ - Bean 工具(BeanCopier, ObjectCopier, PropertyMap)
  • converter/ - 類型轉換器
  • 編譯驗證 ✓

批次 2: 序列化模組

  • json/ - JSON 處理(Jackson 3 Breaking Changes 已修復
  • xml/ - XML 處理
  • csv/ - CSV 處理
  • record/ - 記錄工具(csv 依賴)
  • 編譯驗證 ✓

Jackson 3.x 修復項目

  • com.fasterxml.jackson.*tools.jackson.*(annotation 除外)
  • SerializerProviderSerializationContext
  • JsonProcessingExceptionJacksonException (unchecked)
  • ObjectMapper.configure()JsonMapper.builder().enable/disable()
  • JavaTimeModule 已內建,無需手動註冊
  • 新增 ZonedDateTimeSerializer (tools.jackson 版本)

批次 3: 安全模組

  • auth/ - 認證工具(AbstractAuthenticationToken 修復
  • oauth2/ - OAuth2 工具
  • security/ - 安全工具(lockout, blacklist, tenant, claim)
  • cache/ - 快取(security 依賴)
  • exception/ - 異常處理(security 依賴)
  • error/ - 錯誤處理(security 依賴)
  • http/ - HTTP 客戶端(error 依賴)
  • 編譯驗證 ✓

Spring Security 7.x 修復

  • super(null)super(Collections.emptyList())

批次 4-5: 資料與其他模組

  • entity/ - Entity 工具
  • jpa/ - JPA 工具
  • search/ - RSQL 搜尋
  • file/ - 檔案存儲
  • content/ - 內容檢測
  • image/ - 圖片處理
  • mail/ - 郵件
  • web/ - Web 工具
  • docs/ - 文檔工具
  • measure/ - 度量單位
  • nls/ - 國際化(ObjectMapper import 修復)
  • serial/ - 序列化
  • ehcache/ - Ehcache 配置
  • font/ - 字體
  • helper/ - 工具
  • resource/ - 資源
  • 編譯驗證 ✓

批次 6: resources

  • META-INF/spring.factories
  • 郵件模板 (io/leandev/appfuse/mail/)
  • 完整建置驗證 ✓

Phase 4: 整合驗證

  • app-server-sb4 引用 appfuse-server-sb4
  • 移除重複依賴
  • 驗證啟動成功
  • 驗證基本功能(Health check, H2 Console)

Phase 5: 移植 app-server

  • 複製 Java 原始碼(112 個檔案)
  • 複製 resources
  • 複製測試檔案(16 個檔案)
  • 修復 Breaking Changes - [x] Jackson 3.x: com.fasterxml.jackson.*tools.jackson.* - [x] Jackson 3.x: JsonProcessingExceptionJacksonException - [x] Jackson 3.x: JavaTimeModule 已內建,使用 JsonMapper.builder() - [x] H2Console: PathRequest.toH2Console() → 直接路徑匹配 - [x] HealthIndicator: 套件移至 org.springframework.boot.health.contributor - [x] 測試 Annotation: 套件變更(WebMvcTest, DataJpaTest, etc.)
  • 執行單元測試:207 tests passed
  • 驗證啟動成功 - 啟動時間: 5.376 秒 - Health check: UP - H2 Console: OK

Phase 6: 驗收

  • appfuse-server-sb4 單元測試全部通過 - 491 tests passed - 需先複製測試檔案和資源,並修復 Jackson 3.x / Spring Boot 4.x breaking changes - 需同步 gradle.properties(包含 app.home JVM 參數)
  • app-server-sb4 單元測試全部通過 - 207 tests passed
  • 功能驗收 - [x] Health Check 端點: {"groups":["liveness","readiness"],"status":"UP"} - [x] H2 Console: HTTP 200 - [x] 登入 API: 正確返回錯誤響應格式 - [x] Quartz Scheduler: Scheduler quartzScheduler_$_NON_CLUSTERED started - [x] Ehcache: 8 個 Cache 已創建(loginAttempts, tokenBlacklist, rateLimit, sessions.fast, sessions.durable, users, verificationCodes, configs)

Phase 7: Deprecation 處理

  • EnvironmentPostProcessor 遷移 - 套件從 org.springframework.boot.env 移到 org.springframework.boot - 更新 EnvironConfigInjector.java 的 import - 更新 META-INF/spring.factories 的註冊鍵
  • 其他 deprecation warnings - Ehcache sun.misc.Unsafe 警告:第三方依賴問題,需等待 Ehcache 更新(JDK 26 前需解決)

Breaking Changes 紀錄

基於第一次升級經驗,預先記錄已知的 breaking changes。 新發現的 breaking changes 會持續補充。

Spring Boot Starters

變更類型Spring Boot 3.xSpring Boot 4.x說明
重新命名starter-webstarter-webmvcWeb MVC 應用應使用 webmvc
重新命名starter-oauth2-resource-serverstarter-security-oauth2-resource-serverOAuth2 resource server 前綴加上 security
新增模組N/Aspring-boot-h2consoleH2 Console 獨立為模組(非 starter)
分拆starter-tomcatstarter-tomcat-runtimeWAR 部署用 runtime 版本
新增測試模組N/Astarter-*-test各 starter 有對應的 test 模組

測試 Starter 對照表(Phase 1 新發現):

功能Spring Boot 3.xSpring Boot 4.x
通用測試starter-test拆分為各功能的 test starter
Actuator 測試N/Astarter-actuator-test
Data JPA 測試starter-teststarter-data-jpa-test
Mail 測試N/Astarter-mail-test
Quartz 測試N/Astarter-quartz-test
Security 測試security-teststarter-security-test
Validation 測試N/Astarter-validation-test
WebMvc 測試starter-teststarter-webmvc-test

Jackson

變更類型Jackson 2.xJackson 3.x
Maven groupIdcom.fasterxml.jackson.*tools.jackson.*
Java 套件com.fasterxml.jackson.databindtools.jackson.databind
Java 套件com.fasterxml.jackson.coretools.jackson.core
Annotationscom.fasterxml.jackson.annotation保持不變
ObjectMapper 配置mapper.configure()JsonMapper.builder().enable/disable()
模組註冊mapper.registerModule()JsonMapper.builder().addModule()
Serializer 參數SerializerProviderSerializationContext
Exceptionthrows IOExceptionthrows JacksonException (unchecked)
FeatureSerializationFeature.WRITE_DATES_AS_TIMESTAMPSDateTimeFeature.WRITE_DATES_AS_TIMESTAMPS
自訂 Map 反序列化可自動推斷需要 @JsonCreator 標註
Java 8 Date/Time需註冊 JavaTimeModule已內建支援
StdDeserializer 構造super(null) 可用需傳入實際 Class(如 super(Duration.class)

PropertyMap 修復範例

@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static PropertyMap fromMap(Map<String, Object> map) {
PropertyMap propertyMap = new PropertyMap();
if (map != null) {
propertyMap.putAll(map);
}
return propertyMap;
}

Spring Security

變更類型Spring Security 6.xSpring Security 7.x說明
RequestMatcherAntPathRequestMatcherPathPatternRequestMatcher路徑匹配器變更
AbstractAuthenticationTokensuper(null) 可用super(null) 產生歧義改用 super(Collections.emptyList())

Health Indicator

變更類型Spring Boot 3.xSpring Boot 4.x
套件位置org.springframework.boot.actuate.healthorg.springframework.boot.health.contributor

H2 Console

變更類型Spring Boot 3.xSpring Boot 4.x
PathRequest APIPathRequest.toH2Console()已移除,需改用直接路徑匹配

修復範例

@Value("${spring.h2.console.path:/h2-console}")
private String h2ConsolePath;

http.securityMatcher(h2ConsolePath + "/**")
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());

測試相關 (Test Slices)

變更類型Spring Boot 3.xSpring Boot 4.x
DataJpaTesto.s.boot.test.autoconfigure.orm.jpa.DataJpaTesto.s.boot.data.jpa.test.autoconfigure.DataJpaTest
TestEntityManagero.s.boot.test.autoconfigure.orm.jpa.TestEntityManagero.s.boot.jpa.test.autoconfigure.TestEntityManager
WebMvcTesto.s.boot.test.autoconfigure.web.servlet.WebMvcTesto.s.boot.webmvc.test.autoconfigure.WebMvcTest
AutoConfigureMockMvco.s.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvco.s.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc
EntityScano.s.boot.autoconfigure.domain.EntityScano.s.boot.persistence.autoconfigure.EntityScan

EnvironmentPostProcessor (套件移動)

變更類型Spring Boot 3.xSpring Boot 4.x
套件位置o.s.boot.env.EnvironmentPostProcessoro.s.boot.EnvironmentPostProcessor
spring.factories 鍵org.springframework.boot.env.EnvironmentPostProcessororg.springframework.boot.EnvironmentPostProcessor
舊位置狀態正常@deprecated,將在 4.2.0 移除

遷移步驟

  1. 更新 import:org.springframework.boot.env.EnvironmentPostProcessororg.springframework.boot.EnvironmentPostProcessor
  2. 更新 META-INF/spring.factories 的註冊鍵

執行紀錄

2026-01-22

重新開始升級

基於第一次升級經驗,決定重新執行升級流程:

  • 更新「主版本升級方法論」文檔
  • 重置本升級紀錄
  • 刪除 app-server-sb4 和 appfuse-server-sb4
  • 採用改進後的流程重新開始

Phase 1 完成

1.1 JDK 升級

  • 使用 sdkman 切換到 JDK 25.0.1-tem (Temurin)
  • 設定為系統預設版本

1.2 產生 app-server-sb4

  • 使用 Spring Initializr 產生專案
    • bootVersion=4.0.1
    • javaVersion=25
    • packaging=war
    • dependencies: web, data-jpa, security, validation, actuator, mail, quartz, h2, lombok
  • 編譯和啟動驗證成功
    • 啟動時間: 2.053 秒
    • Tomcat 11.0.15
    • Hibernate ORM 7.2.0.Final

新發現的 Starter 變更

  1. spring-boot-h2console - H2 Console 獨立為專用模組(非 starter)
  2. starter-*-test - 每個功能 starter 都有對應的 test starter(不再只有 starter-test)
  3. providedRuntime("starter-tomcat-runtime") - WAR 部署用的 Tomcat runtime

Phase 2 完成

建立 appfuse-server-sb4

  • 建立 java-library 專案結構
  • 配置 build.gradle.kts(調整 Spring Boot 4.x 依賴)
    • starter-webmvc 取代 starter-web
    • starter-security-oauth2-resource-server 取代 starter-oauth2-resource-server
    • tools.jackson.dataformat:jackson-dataformat-csv 取代 com.fasterxml.jackson.dataformat
    • 測試依賴拆分為各功能的 test starter
  • 配置 Composite Build(app-server-sb4 引用 appfuse-server-sb4)
  • 空專案編譯驗證成功

Phase 3 完成

逐模組移植 appfuse-server(30 個模組):

  • 批次 1(基礎工具): env, bean, converter ✓
  • 批次 2(序列化): json, xml, csv, record ✓
  • 批次 3(安全): auth, oauth2, security, cache, exception, error, http ✓
  • 批次 4-5(資料與其他): entity, jpa, search, file, content, image, mail, web, docs, measure, nls, serial, ehcache, font, helper, resource ✓
  • 批次 6(resources): META-INF/spring.factories, 郵件模板 ✓

主要修復項目

  1. Jackson 3.x:

    • 套件 com.fasterxml.jackson.*tools.jackson.*
    • SerializerProviderSerializationContext
    • JsonProcessingExceptionJacksonException
    • ObjectMapper.configure()JsonMapper.builder()
    • 新增 ZonedDateTimeSerializer
  2. Spring Security 7.x:

    • super(null)super(Collections.emptyList())

建置狀態: BUILD SUCCESSFUL

Phase 4 完成

整合驗證

  • 在 app-server-sb4 的 build.gradle.kts 加入 appfuse-server-sb4 依賴
    • implementation("io.leandev.appfuse.webapp:appfuse-server-sb4")
  • 移除與 appfuse-server-sb4 重複的依賴(透過 api scope 傳遞)
    • 移除: starter-actuator, starter-data-jpa, starter-mail, starter-quartz, starter-security, starter-validation, starter-webmvc
    • 保留: spring-boot-h2console, h2, tomcat-runtime(app-server 特有)
  • 編譯驗證成功
  • 啟動驗證成功
    • 啟動時間: 2.395 秒
    • EnvironConfigInjector 正常運作(META-INF/spring.factories 有效)
  • Health check 端點正常: {"groups":["liveness","readiness"],"status":"UP"}
  • H2 Console 返回 401(預期行為,Security 配置在 Phase 5 處理)

Phase 5 完成

移植 app-server

  • 複製 Java 原始碼(112 個檔案,17 個模組目錄)
  • 複製 resources(application.yml, app-server.yml, data/, doc/)
  • 複製測試檔案(16 個檔案)

修復 Breaking Changes

  1. Jackson 3.x:

    • 套件 com.fasterxml.jackson.*tools.jackson.*
    • JsonProcessingExceptionJacksonException
    • JavaTimeModule 已內建,使用 JsonMapper.builder()
    • StdDeserializer 不再接受 null,需傳入實際類型
    • PropertyMap 需要 @JsonCreator 標註才能正確反序列化
  2. H2Console:

    • PathRequest.toH2Console() → 使用 @Value 注入 spring.h2.console.path + 直接路徑匹配
  3. HealthIndicator:

    • 套件 org.springframework.boot.actuate.healthorg.springframework.boot.health.contributor
  4. 測試 Annotation 套件變更:

    • o.s.boot.test.autoconfigure.web.servleto.s.boot.webmvc.test.autoconfigure
    • o.s.boot.test.autoconfigure.orm.jpa.DataJpaTesto.s.boot.data.jpa.test.autoconfigure.DataJpaTest
    • o.s.boot.test.autoconfigure.orm.jpa.TestEntityManagero.s.boot.jpa.test.autoconfigure.TestEntityManager

驗證結果

  • 單元測試: 207 tests passed
  • 啟動時間: 5.376 秒
  • Health check: UP
  • H2 Console: OK

Phase 6 完成

appfuse-server-sb4 單元測試

  • 複製測試檔案:src/test/java/io/leandev/appfuse/*
  • 複製測試資源:src/test/resources/*
  • 同步 gradle.properties(含 app.home JVM 參數)
  • 修復 Jackson 3.x breaking changes:
    • tools.jackson.datatype.jsr310.ser.ZonedDateTimeSerializerio.leandev.appfuse.json.ZonedDateTimeSerializer
    • ObjectMapper.configure()JsonMapper.builder().disable()
    • SerializationFeature.WRITE_DATES_AS_TIMESTAMPSDateTimeFeature.WRITE_DATES_AS_TIMESTAMPS
  • 測試結果: 491 tests passed

app-server-sb4 單元測試

  • 新增 gradle.properties(含 app.home JVM 參數)
  • 測試結果: 207 tests passed

功能驗收

  • Health Check: {"groups":["liveness","readiness"],"status":"UP"}
  • H2 Console: HTTP 200
  • 登入 API: 正確返回錯誤響應格式 {"detail":"Invalid username or password",...}
  • Quartz Scheduler: Scheduler quartzScheduler_$_NON_CLUSTERED started
  • Ehcache: 8 個 Cache 已創建

Phase 7 完成

EnvironmentPostProcessor 遷移

  • 套件從 org.springframework.boot.env.EnvironmentPostProcessor 移到 org.springframework.boot.EnvironmentPostProcessor
  • 更新 EnvironConfigInjector.java 的 import
  • 更新 META-INF/spring.factories 的註冊鍵
  • 編譯和測試驗證通過

其他 deprecation warnings

  • Ehcache sun.misc.Unsafe::objectFieldOffset 警告
    • 這是第三方依賴(Ehcache 3.11.1)的問題
    • 根據 JEP 471,JDK 26 將禁止使用這些 API
    • 需等待 Ehcache 更新以使用 VarHandle API 替代
    • 目前不影響功能,僅為警告訊息

第一次升級經驗教訓

2026-01-21 ~ 2026-01-22 第一次升級的關鍵發現,供第二次升級參考。

遇到的問題與解決方案

1. 遺漏模組

問題:Phase 3 一次複製所有模組後,Phase 5 才發現遺漏了 serial/, ehcache/, font/, helper/, resource/

原因:事前沒有完整盤點 appfuse-server 的模組清單。

解決方案:Phase 0 必須完整盤點所有 Java package 目錄。

2. 遺漏 META-INF/spring.factories

問題:app-server-sb4 啟動失敗,app.jwt.private-key 無法解析。

原因EnvironmentPostProcessor 需要透過 META-INF/spring.factories 註冊,但沒有複製這個檔案。

解決方案:Phase 0 盤點時必須包含 src/main/resources/META-INF/ 目錄。

3. PropertyMap Jackson 反序列化失敗

問題:17 個 Controller 測試回傳 400 Bad Request。

原因:Jackson 3.x 對於實作 Map 介面但不是標準 Map 的類別,需要明確的 @JsonCreator 標註。

解決方案

// 在 PropertyMap 類別中加入
@JsonCreator(mode = JsonCreator.Mode.DELEGATING)
public static PropertyMap fromMap(Map<String, Object> map) {
PropertyMap propertyMap = new PropertyMap();
if (map != null) {
propertyMap.putAll(map);
}
return propertyMap;
}

發現時機:Phase 6 測試階段。應該在 Phase 3 批次 1 移植 bean/ 模組時就執行測試發現。

4. Jackson 套件名稱變更

問題:編譯錯誤,找不到 com.fasterxml.jackson.databind

原因:Jackson 3.x 將套件從 com.fasterxml.jackson 改為 tools.jackson

涉及檔案:約 24 個檔案需要修改 import。

解決方案:批量替換 import:

# databind
find . -name "*.java" -exec sed -i '' \
's/com\.fasterxml\.jackson\.databind/tools.jackson.databind/g' {} \;

# core
find . -name "*.java" -exec sed -i '' \
's/com\.fasterxml\.jackson\.core/tools.jackson.core/g' {} \;

# dataformat
find . -name "*.java" -exec sed -i '' \
's/com\.fasterxml\.jackson\.dataformat/tools.jackson.dataformat/g' {} \;

注意com.fasterxml.jackson.annotation 保持不變!

5. AbstractAuthenticationToken 構造函數歧義

問題:編譯錯誤,super(null) 產生歧義。

原因:Spring Security 7.x 新增了 AbstractAuthenticationBuilder 構造函數。

解決方案

// Before (Spring Security 6.x)
super(null);

// After (Spring Security 7.x)
super(Collections.emptyList());

6. 測試 import 變更

問題:測試編譯錯誤,找不到 @DataJpaTest 等 annotation。

原因:Spring Boot 4.x 將測試相關類別移到了新的 package。

解決方案:批量替換 import:

# WebMvcTest, AutoConfigureMockMvc
find . -name "*.java" -exec sed -i '' \
's/org.springframework.boot.test.autoconfigure.web.servlet/org.springframework.boot.webmvc.test.autoconfigure/g' {} \;

# DataJpaTest
find . -name "*.java" -exec sed -i '' \
's/org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest/org.springframework.boot.data.jpa.test.autoconfigure.DataJpaTest/g' {} \;

# TestEntityManager
find . -name "*.java" -exec sed -i '' \
's/org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager/org.springframework.boot.jpa.test.autoconfigure.TestEntityManager/g' {} \;

# EntityScan
find . -name "*.java" -exec sed -i '' \
's/org.springframework.boot.autoconfigure.domain.EntityScan/org.springframework.boot.persistence.autoconfigure.EntityScan/g' {} \;

7. ObjectMapperBuilder 重構

問題:Jackson 3.x 不再支援 ObjectMapper.configure() 方法。

解決方案

// Before (Jackson 2.x)
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
mapper.registerModule(new JavaTimeModule());

// After (Jackson 3.x)
ObjectMapper mapper = JsonMapper.builder()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS)
// JavaTimeModule 已內建,無需手動註冊
.build();

8. Serializer/Deserializer API 變更

問題SerializerProvider 找不到。

解決方案

// Serializer
// Before: public void serialize(T value, JsonGenerator gen, SerializerProvider provider) throws IOException
// After: public void serialize(T value, JsonGenerator gen, SerializationContext ctxt) throws JacksonException

// Deserializer
// Before: public T deserialize(JsonParser p, DeserializationContext ctxt) throws IOException
// After: public T deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException

流程改進建議

問題原流程改進後流程
遺漏模組Phase 3 一次複製所有Phase 0 完整盤點 + Phase 3 逐批移植
遺漏 META-INFPhase 5 才發現Phase 0 盤點 resources(含 META-INF)
Jackson 問題Phase 6 測試才發現Phase 3 批次 2 移植時執行測試
測試 importPhase 6 批量修復Phase 3/5 複製測試時同步修復

參考資源