Spring Boot 4.0 升級紀錄
本文檔記錄 appfuse-server 和 app-server 從 Spring Boot 3.5.x 升級到 4.0.x 的完整過程, 同時作為升級計劃、進度追蹤和過程紀錄。
第二次升級:基於第一次升級的經驗,採用改進後的方法論重新執行。
升級概覽
| 項目 | 升級前 | 升級後 |
|---|---|---|
| JDK | 24 (Temurin-24.0.1) | 25 |
| Spring Boot | 3.5.6 | 4.0.1 |
| Spring Security | 6.x | 7.x |
| Hibernate | 6.x | 7.x |
| Jackson | 2.x (com.fasterxml.jackson) | 3.x (tools.jackson) |
| Tomcat | 10.x | 11.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
升級策略
採用「從乾淨框架逐步移植」策略,詳見 主版本升級方法論。
本次改進重點:
- Phase 0 完整盤點(模組、resources、META-INF)
- Phase 3 逐模組移植(不一次複製所有)
- 每個批次都執行測試
- 區分 Breaking Change vs Deprecation
事前準備
appfuse-server 模組清單(30 個)
| 批次 | 模組 | 說明 | 預期 Breaking Changes |
|---|---|---|---|
| 1 | env/ | 環境配置 | EnvironmentPostProcessor deprecated |
| 1 | bean/ | Bean 工具 | PropertyMap 需 @JsonCreator |
| 1 | converter/ | 類型轉換器 | - |
| 2 | json/ | JSON 處理 | Jackson 3 套件變更 |
| 2 | xml/ | XML 處理 | - |
| 2 | csv/ | CSV 處理 | Jackson 3 套件變更 |
| 3 | auth/ | 認證工具 | AbstractAuthenticationToken |
| 3 | oauth2/ | OAuth2 工具 | - |
| 3 | security/ | 安全工具 | - |
| 4 | entity/ | Entity 工具 | - |
| 4 | jpa/ | JPA 工具 | - |
| 4 | search/ | RSQL 搜尋 | - |
| 4 | cache/ | 快取 | - |
| 5 | file/ | 檔案存儲 | - |
| 5 | content/ | 內容檢測 | - |
| 5 | image/ | 圖片處理 | - |
| 5 | mail/ | 郵件 | - |
| 5 | web/ | Web 工具 | - |
| 5 | docs/ | 文檔工具 | - |
| 5 | http/ | HTTP 客戶端 | - |
| 5 | exception/ | 異常處理 | - |
| 5 | error/ | 錯誤處理 | - |
| 5 | measure/ | 度量單位 | - |
| 5 | nls/ | 國際化 | - |
| 5 | record/ | 記錄工具 | - |
| 5 | serial/ | 序列化 | - |
| 5 | ehcache/ | Ehcache 配置 | - |
| 5 | font/ | 字體 | - |
| 5 | helper/ | 工具 | - |
| 5 | resource/ | 資源 | - |
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-server | → starter-security-oauth2-resource-server |
starter-validation | - |
starter-web | → starter-webmvc |
ehcache | - |
httpclient5 | - |
jjwt-api (0.12.5) | - |
implementation scope:
| 依賴 | 說明 |
|---|---|
kryo | Java 序列化 |
dom4j | XML 處理 |
jaxen | XPath |
rsql-parser | RSQL 搜尋解析 |
jackson-dataformat-csv | → tools.jackson |
twelvemonkeys-imageio | 圖片格式支援 |
batik-transcoder | SVG 處理 |
runtimeOnly scope:
| 依賴 | 說明 |
|---|---|
jjwt-impl | JJWT 實作 |
jjwt-jackson | JJWT Jackson |
compileOnly scope(由應用層引入):
| 依賴 | 說明 |
|---|---|
lombok | - |
aws-sdk:s3 | S3/MinIO |
azure-storage-blob | Azure Blob |
sshd-sftp | SFTP |
app-server 模組清單(17 個)
| 模組 | 說明 | 預期 Breaking Changes |
|---|---|---|
actuator/ | 自訂 Actuator | HealthIndicator 套件變更 |
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-sftp | SFTP | - |
aws-sdk:s3 | S3/MinIO | - |
azure-storage-blob | Azure | - |
h2 | 嵌入式資料庫 | - |
mssql-jdbc | SQL Server | - |
mysql-connector-j | MySQL | - |
ojdbc11 | Oracle | - |
postgresql | PostgreSQL | - |
starter-tomcat | WAR 部署 | → starter-tomcat-runtime |
預期 Breaking Changes
基於第一次升級經驗和官方 Migration Guide:
| 項目 | 影響模組 | 類型 | 處理方式 |
|---|---|---|---|
starter-web → starter-webmvc | build.gradle | Breaking | Phase 2 配置 |
starter-oauth2-resource-server → starter-security-oauth2-resource-server | build.gradle | Breaking | Phase 2 配置 |
| Jackson 2 → 3 套件名稱 | json/, csv/ | Breaking | Phase 3 批次 2 |
AbstractAuthenticationToken 構造函數歧義 | auth/ | Breaking | Phase 3 批次 3 |
HealthIndicator 套件位置 | 消費者 | Breaking | Phase 5 |
H2Console PathRequest.toH2Console() 移除 | 消費者 | Breaking | Phase 5 |
| 測試 import 變更(DataJpaTest 等) | 所有測試 | Breaking | Phase 3, 5 |
EnvironmentPostProcessor deprecated | env/ | Deprecation | Phase 7 |
| JJWT vs Nimbus | auth/ | 評估 | 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 除外)SerializerProvider→SerializationContextJsonProcessingException→JacksonException(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:JsonProcessingException→JacksonException- [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.homeJVM 參數) - 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.x | Spring Boot 4.x | 說明 |
|---|---|---|---|
| 重新命名 | starter-web | starter-webmvc | Web MVC 應用應使用 webmvc |
| 重新命名 | starter-oauth2-resource-server | starter-security-oauth2-resource-server | OAuth2 resource server 前綴加上 security |
| 新增模組 | N/A | spring-boot-h2console | H2 Console 獨立為模組(非 starter) |
| 分拆 | starter-tomcat | starter-tomcat-runtime | WAR 部署用 runtime 版本 |
| 新增測試模組 | N/A | starter-*-test | 各 starter 有對應的 test 模組 |
測試 Starter 對照表(Phase 1 新發現):
| 功能 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| 通用測試 | starter-test | 拆分為各功能的 test starter |
| Actuator 測試 | N/A | starter-actuator-test |
| Data JPA 測試 | starter-test | starter-data-jpa-test |
| Mail 測試 | N/A | starter-mail-test |
| Quartz 測試 | N/A | starter-quartz-test |
| Security 測試 | security-test | starter-security-test |
| Validation 測試 | N/A | starter-validation-test |
| WebMvc 測試 | starter-test | starter-webmvc-test |
Jackson
| 變更類型 | Jackson 2.x | Jackson 3.x |
|---|---|---|
| Maven groupId | com.fasterxml.jackson.* | tools.jackson.* |
| Java 套件 | com.fasterxml.jackson.databind | tools.jackson.databind |
| Java 套件 | com.fasterxml.jackson.core | tools.jackson.core |
| Annotations | com.fasterxml.jackson.annotation | 保持不變 |
| ObjectMapper 配置 | mapper.configure() | JsonMapper.builder().enable/disable() |
| 模組註冊 | mapper.registerModule() | JsonMapper.builder().addModule() |
| Serializer 參數 | SerializerProvider | SerializationContext |
| Exception | throws IOException | throws JacksonException (unchecked) |
| Feature | SerializationFeature.WRITE_DATES_AS_TIMESTAMPS | DateTimeFeature.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.x | Spring Security 7.x | 說明 |
|---|---|---|---|
| RequestMatcher | AntPathRequestMatcher | PathPatternRequestMatcher | 路徑匹配器變更 |
| AbstractAuthenticationToken | super(null) 可用 | super(null) 產生歧義 | 改用 super(Collections.emptyList()) |
Health Indicator
| 變更類型 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| 套件位置 | org.springframework.boot.actuate.health | org.springframework.boot.health.contributor |
H2 Console
| 變更類型 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| PathRequest API | PathRequest.toH2Console() | 已移除,需改用直接路徑匹配 |
修復範例:
@Value("${spring.h2.console.path:/h2-console}")
private String h2ConsolePath;
http.securityMatcher(h2ConsolePath + "/**")
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
測試相關 (Test Slices)
| 變更類型 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| DataJpaTest | o.s.boot.test.autoconfigure.orm.jpa.DataJpaTest | o.s.boot.data.jpa.test.autoconfigure.DataJpaTest |
| TestEntityManager | o.s.boot.test.autoconfigure.orm.jpa.TestEntityManager | o.s.boot.jpa.test.autoconfigure.TestEntityManager |
| WebMvcTest | o.s.boot.test.autoconfigure.web.servlet.WebMvcTest | o.s.boot.webmvc.test.autoconfigure.WebMvcTest |
| AutoConfigureMockMvc | o.s.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc | o.s.boot.webmvc.test.autoconfigure.AutoConfigureMockMvc |
| EntityScan | o.s.boot.autoconfigure.domain.EntityScan | o.s.boot.persistence.autoconfigure.EntityScan |
EnvironmentPostProcessor (套件移動)
| 變更類型 | Spring Boot 3.x | Spring Boot 4.x |
|---|---|---|
| 套件位置 | o.s.boot.env.EnvironmentPostProcessor | o.s.boot.EnvironmentPostProcessor |
| spring.factories 鍵 | org.springframework.boot.env.EnvironmentPostProcessor | org.springframework.boot.EnvironmentPostProcessor |
| 舊位置狀態 | 正常 | @deprecated,將在 4.2.0 移除 |
遷移步驟:
- 更新 import:
org.springframework.boot.env.EnvironmentPostProcessor→org.springframework.boot.EnvironmentPostProcessor - 更新
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 變更:
spring-boot-h2console- H2 Console 獨立為專用模組(非 starter)starter-*-test- 每個功能 starter 都有對應的 test starter(不再只有 starter-test)providedRuntime("starter-tomcat-runtime")- WAR 部署用的 Tomcat runtime
Phase 2 完成
建立 appfuse-server-sb4:
- 建立 java-library 專案結構
- 配置 build.gradle.kts(調整 Spring Boot 4.x 依賴)
starter-webmvc取代starter-webstarter-security-oauth2-resource-server取代starter-oauth2-resource-servertools.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, 郵件模板 ✓
主要修復項目:
-
Jackson 3.x:
- 套件
com.fasterxml.jackson.*→tools.jackson.* SerializerProvider→SerializationContextJsonProcessingException→JacksonExceptionObjectMapper.configure()→JsonMapper.builder()- 新增
ZonedDateTimeSerializer
- 套件
-
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:
-
Jackson 3.x:
- 套件
com.fasterxml.jackson.*→tools.jackson.* JsonProcessingException→JacksonExceptionJavaTimeModule已內建,使用JsonMapper.builder()StdDeserializer不再接受 null,需傳入實際類型PropertyMap需要@JsonCreator標註才能正確反序列化
- 套件
-
H2Console:
PathRequest.toH2Console()→ 使用@Value注入spring.h2.console.path+ 直接路徑匹配
-
HealthIndicator:
- 套件
org.springframework.boot.actuate.health→org.springframework.boot.health.contributor
- 套件
-
測試 Annotation 套件變更:
o.s.boot.test.autoconfigure.web.servlet→o.s.boot.webmvc.test.autoconfigureo.s.boot.test.autoconfigure.orm.jpa.DataJpaTest→o.s.boot.data.jpa.test.autoconfigure.DataJpaTesto.s.boot.test.autoconfigure.orm.jpa.TestEntityManager→o.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.homeJVM 參數) - 修復 Jackson 3.x breaking changes:
tools.jackson.datatype.jsr310.ser.ZonedDateTimeSerializer→io.leandev.appfuse.json.ZonedDateTimeSerializerObjectMapper.configure()→JsonMapper.builder().disable()SerializationFeature.WRITE_DATES_AS_TIMESTAMPS→DateTimeFeature.WRITE_DATES_AS_TIMESTAMPS
- 測試結果: 491 tests passed
app-server-sb4 單元測試:
- 新增 gradle.properties(含
app.homeJVM 參數) - 測試結果: 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-INF | Phase 5 才發現 | Phase 0 盤點 resources(含 META-INF) |
| Jackson 問題 | Phase 6 測試才發現 | Phase 3 批次 2 移植時執行測試 |
| 測試 import | Phase 6 批量修復 | Phase 3/5 複製測試時同步修復 |