JBoss EAP × Spring Boot 升級入門:先懂執行路徑,再驗證行為不變
拿到一個舊 Java 系統,第一件事常是打開 pom.xml,開始換版本。等到應用可以啟動,卻發現原本回傳 JSON 的錯誤變成 HTML、MDB 多收了一次訊息,或批次結束了卻沒有提交資料。這些是本文用來說明的情境,不是已發生在某個專案的事故紀錄。
這份教學以 EAP 6.4、JDK 7/8、Spring 3/4 升級到 EAP 8.1、JDK 21、Spring Boot 3.5.16 為參考情境。目標是用同一組案例比較 HTTP、資料庫、MQ 與 log 的行為。資料查核日為 2026-10-03;本文是官方文件解讀與驗證設計,尚未在這組企業環境執行整合測試。
先讀這一段:EAP、Spring、Boot 各自負責什麼
JBoss EAP 是應用伺服器,提供 Servlet 容器、EJB/MDB 執行環境,以及可設定的資料來源、交易與管理能力。Spring Framework 是應用程式裡的框架,管理 Bean、相依注入、MVC 與交易整合。Spring Boot 則用 starter、版本管理與條件式自動設定,把 Spring 應用組裝起來。導入 Boot 後,原有的 Spring 業務程式仍然存在。
Bean 可以先理解成由 Spring 建立並管理的物件;相依注入就是由容器把需要的物件交給它。EAP 也有自己的元件管理方式。Spring Bean、CDI Bean 與 EJB 不會只因為放在同一個 EAR 就自動變成同一套生命週期;跨邊界要有明確的整合方式。
WAR on EAP:
Client → EAP / Undertow → Filters → DispatcherServlet
→ Controller → Service → Repository → DataSource → DB
Executable Spring Boot application:
Client → Embedded web server → Filters → DispatcherServlet
→ Controller → Service → Repository → DataSource → DB
MDB on EAP:
MQ → Jakarta resource adapter → EAP MDB → Business logic
Standalone batch:
Scheduler → JVM main / Boot runner → Business logic → DB / MQ兩種 Web 部署最大的差別是誰啟動並管理 HTTP 容器。外部 EAP 部署由 EAP 載入 WAR;可執行 Boot 應用通常由 main 啟動內嵌伺服器。MDB 與獨立批次又有不同入口,不能把整批系統一律套成 java -jar。
版本先對齊:目標清單不等於已通過的相容矩陣
| 項目 | 原有環境 | 目標 |
|---|---|---|
| Java/EAP | JDK 7/8;EAP 6.4 | JDK 21;EAP 8.1 |
| Spring/Security | 3.2.9/4.3.x;3.x/4.1 | Framework 6.2.19;Security 6.5.11 |
| Boot/Hibernate | 未使用 Boot;Hibernate 4.2/4.3 | Boot 3.5.16;Hibernate 6.6 系列 |
| REST/建置 | CXF 2.6 JAX-RS;Ant/Maven | Spring MVC 6.2;Maven |
| MQ/DB 驅動 | IBM MQ client 9.0.4;舊 JDBC jar | MQ client 9.4.5.1;DB2 11.5 線相關驅動 |
| 前端 | Angular 11 | Angular 22 |
查核時 Spring Boot 3.5.16 的官方依賴表列出 Framework 6.2.19、Security 6.5.11 與 Hibernate ORM 6.6.53.Final。Red Hat 的 EAP 8.1.0 元件表則列出 Hibernate ORM 6.6.18.Final。這正好說明「都是 6.6」不足以證明版本相同;EAP 修補版還可能更新元件。先決定使用 EAP 提供的 JPA provider,還是由應用攜帶,再檢查部署模組與供應商支援。
Boot 3.5 至少需要 Java 17,傳統部署要求 Servlet 5.0 以上;EAP 8.1 的 Undertow 提供 Servlet 6.0。這只滿足其中一層條件,沒有證明所有函式庫與設定都相容。Boot 4.0 的外部容器要求提高到 Servlet 6.1,因此不能在這份 EAP 8.1 教學中直接換成 Boot 4。
MQ client 的版本也不等於 Queue Manager、RAR 與作業系統的支援組合已確認。「DB2 11.5 線」則還不足以鎖定 JDBC 驅動:要補上 jar/Maven 座標、實際版本及資料庫服務端版本。這些應留在環境清單,不能由 AI 補猜。
javax → jakarta:先判斷 API 屬於誰
| 類型 | 例子 | 處理原則 |
|---|---|---|
| Jakarta EE API | javax.servlet、javax.persistence、javax.jms、javax.ejb | 遷移到對應 jakarta 命名空間及相容版本 |
| Java SE API | javax.sql、javax.naming、javax.crypto | 仍屬 JDK;不要全域取代 |
| 第三方/歷史 API | 舊 JAXB、驗證器、產生的 client code | 核對實際來源與相容版本,重新生成或逐項遷移 |
JDK 21 仍有 javax.sql.DataSource 與 javax.naming.InitialContext。看到 javax 就替換,會把正確的 JDBC 與 JNDI 程式也改壞。只有 import 改完也不夠:舊 jar、XML schema、服務載入設定與反射使用的類別名稱,都可能仍指向舊 API。
rg -n "javax\.|jakarta\." src pom.xml
mvn -version
mvn help:effective-pom -Doutput=target/effective-pom.xml
mvn dependency:tree -Dverbose這些指令用來蒐集線索,不會自動證明遷移正確。請在可還原的開發副本執行 Maven;專案 plugin 可能下載相依套件或執行設定好的工作。effective POM 顯示合併後的設定,dependency tree 顯示 Maven 解出的依賴;兩者都還沒有告訴你 EAP 執行時實際載入哪個模組。
Web 路徑:從一個 HTTP 請求理解 Spring MVC
先把 Controller 想成 HTTP 邊界:取得路徑、參數與本文,呼叫 Service,再產生回應。Service 放業務規則,Repository 處理資料存取。DispatcherServlet 負責把請求分派給 handler;前面的 Filter 可能已經因認證或其他規則拒絕請求,因此沒有走到 Controller 不一定是路由壞掉。
package example.migration;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
class PingController {
@GetMapping("/api/ping")
java.util.Map<String, String> ping() {
return java.util.Map.of("status", "ok");
}
}這是理解路由的片段,需放在 Spring 掃描範圍內,並具備 MVC 相依套件與應用入口;它不是完整可部署專案。這個固定回應不會存取 DB 或 MQ,所以成功呼叫只能確認這條 HTTP 路徑,不能代表資料庫與訊息交易正常。
從 CXF 的 JAX-RS 改成 Spring MVC,需逐項對應路徑、HTTP method、參數預設值、Content-Type、驗證錯誤、例外轉換與 JSON 序列化。@Path 換成 @RequestMapping 只是入口。假設舊服務的日期字串、null 欄位或錯誤格式改變,即使業務 Service 沒改,Angular 與其他呼叫端仍可能受到影響。
Security 3/4 到 6 也要獨立列為遷移工作。比對未登入、權限不足、session 到期、CSRF 與 CORS 情境;不要為了讓案例變綠就關掉原有安全控制。升級時發現舊漏洞,應把修正列成經審核的行為差異,不能把不安全的舊行為當成永遠必須保留的契約。
WAR 放到 EAP:先分清楚誰啟動、誰提供函式庫
Spring MVC 應用採傳統 WAR 部署時,Boot 官方做法包含 SpringBootServletInitializer、WAR packaging,以及把內嵌 Servlet 容器相依標成 provided。這是組裝方向;EAP 的 logging、JPA、JAX-RS 與模組載入仍須依應用核對。Spring WebFlux 不支援這種傳統 WAR 部署。
// Fragment for an existing Spring Boot 3.5 application
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(
SpringApplicationBuilder builder) {
return builder.sources(Application.class);
}
}外部容器載入 WAR 時使用 Servlet 初始化路徑,不能只看 main() 能否執行。server.port 主要控制 Boot 內嵌伺服器,不會重新設定 EAP 的 listener。若 404,先確認部署成功、context root、反向代理前綴與 Controller mapping。EAP 的 standalone 模式表示單一伺服器的管理方式,也不等於 Boot 的可執行 JAR。
使用 EAP 管理的 DataSource 時,Spring Boot 可以透過 spring.datasource.jndi-name 查找。JNDI 像資源名稱簿;設定同一個名稱,才找得到 EAP 已設定的資料來源。連線池參數應到真正建立連線池的一方調整,不能假設 application.yaml 裡的 Hikari 設定一定會生效。
# Example only: this resource must already exist in EAP
spring.datasource.jndi-name=java:jboss/datasources/ExampleDSHibernate 與交易:查到資料,不代表提交方式沒變
Hibernate 把物件操作轉成資料庫存取。跨越 4.x 到 6.6 時,要檢查查詢語法、型別對應、識別碼生成、lazy loading,以及 flush/commit 的時點。Hibernate 的遷移文件有逐代差異;不能只讀 6.6 的小版本變更,就當成已涵蓋從 4.x 以來的全部修改。
交易先問「由誰開始、哪些資源參與、何時提交、失敗時誰回滾」。Spring 的 @Transactional 並不自動代表所有 DB 與 MQ 操作都加入同一筆 XA 交易。需要核對 transaction manager、DataSource、Resource Adapter 與實際資源參與情況。只看 annotation 看不出完整的執行契約。
一個有用的負向案例是:DB 寫入後,在預定的測試注入點讓流程失敗。接著比較 DB 有沒有留下資料、MQ 是否確認或重新投遞,以及重跑是否造成重複業務結果。預期結果必須先由原系統契約決定;測試環境需使用隔離的資料與佇列。
MDB 與 IBM MQ:接收者的生命週期不能順手改掉
MDB 是由 EAP 的 EJB 容器管理的訊息驅動元件。IBM MQ Resource Adapter 把訊息系統接到應用伺服器,再把輸入訊息交給 MDB。獨立批次使用的 MQ client jar 與部署到 EAP 的 RAR 是不同交付物,不能只升 client 版本就認定 MDB 已完成升級。
IBM 提供 Java EE/JMS 用的 wmq.jmsra.rar,以及 Jakarta Messaging 用的 wmq.jakarta.jmsra.rar。EAP 8.1 的 Jakarta 路徑應核對後者及其版本支援,並檢查 activation 設定、JNDI 名稱、selector、並行消費、交易模式和失敗重新投遞。不要在同一應用混用 JMS 2.0 與 Jakarta Messaging API。
把 MDB 改寫成 @JmsListener 會改變管理與整合方式,應另開設計決策。若目標是行為保持,先保留消費邊界,驗證訊息本文、相關識別、業務屬性、順序要求與重複處理結果。不要用清空正式佇列的方式建立測試基準。
獨立批次與 Angular:各自驗收,不綁成一次大改寫
批次程式可能由排程器直接啟動 JVM,根本沒有 EAP。若引入 Boot,先決定它是否需要 Web 功能,再保留原有參數、工作目錄、字元編碼、時區、exit code、提交頻率與重新啟動語意。原先讀到一半失敗的檔案,下次究竟重跑全部還是接續,必須先寫成案例。
Ant 轉 Maven 時,盤點的不只是相依版本,還包括資源複製、產碼、打包排除、manifest 與環境參數。JDK 21 的編譯成功也不代表反射、移除的 JDK 元件或執行期選項都相容;需沿 Java 遷移指南檢查實際使用的 API。
Angular 11 到 22 建議依官方 Update Guide 逐個主要版本處理,另核對 Node.js、TypeScript 與 RxJS 的版本要求。前後端可平行工作,但應以固定 API 案例集合對接。API 回傳相同還不夠,前端仍需測登入、錯誤顯示、日期格式、路由與實際使用流程。
把「行為不變」寫成可以比較的證據
先在隔離的舊環境執行案例並保存原始輸出,再把相同初始資料、輸入與外部依賴條件帶到新環境。涉及副作用的案例需要各自還原資料快照,不能把同一筆付款或訊息對正式環境執行兩次。非同步處理須先定義觀察完成的條件與等待上限。
| 證據 | 至少比較 | 差異處理 |
|---|---|---|
| HTTP | status、header、schema、型別、null、時間/金額 | 欄位與錯誤格式依契約比對 |
| DB | 新增/修改/刪除、約束、提交與回滾 | 排序僅用於沒有順序契約的結果 |
| MQ | body、業務屬性、數量、重新投遞、相關識別 | 動態 ID 以關聯規則比較,不能一律忽略 |
| Log | 關鍵事件、severity、錯誤、追蹤串接 | 時間戳/thread 名稱依核准規則正規化 |
保留原始證據與正規化後的結果。哪些欄位可忽略、哪些排序可調整,要事前寫進可版本控制的規則;AI 不應為了讓差異消失,自行刪掉失敗訊息或把所有數字遮掉。新 runtime 的啟動 log 不一定逐字相同,業務稽核事件卻可能需要嚴格對應。無法對應的差異列為未決,交由負責人判斷。
case: order-validation-example
fixture: synthetic-data-v1
baseline: old-build-id
candidate: new-build-id
request: cases/order-validation/request.json
observations:
http: [status, headers, body]
db: [rows_before, rows_after]
mq: [messages, redelivery_observations]
logs: [business_events, errors]
normalization: reviewed-rules-v1
result: pending
unexplained_differences: []
# Teaching template only; no comparison has been executed.看到錯誤,先定位層次
| 現象 | 先看哪裡 |
|---|---|
| 404 | 部署狀態、context root、代理路徑、MVC mapping |
| 401/403 或轉往登入頁 | Security filter、session/token、CSRF、授權規則 |
| ClassNotFound/NoSuchMethod | Maven 依賴、WAR 內容、EAP 模組與實際載入來源 |
| 找不到 DataSource | JNDI 名稱、EAP 設定、應用啟用的設定來源 |
| MDB 沒收到或重複處理 | RAR、activation、selector、交易與重新投遞 |
| 批次完成但資料不對 | 參數、資料快照、交易邊界、exit code 與重跑語意 |
讓 AI 交接一個改動,而不是再交給你一疊文件
先挑一個 Web 端點或一個 MDB 案例,填完環境與行為契約,再請 AI 改一個邊界。交付時只要求:行為前後差異、設計依據、三個以內必看位置、驗證證據、未決事項。需要補理解時,問「這個例外會在哪裡讓交易回滾」,比「解釋整個 Spring」更容易對照實際程式。
下載升級盤點與行為比對工作表(Markdown)今天可以先做的練習:選一個你負責的功能,用自己的話說出入口、容器、交易擁有者與四種證據。遇到無法回答的欄位,就只查那一段。等到能指出失敗時要回頭看哪份設定與哪筆紀錄,再擴大到下一個功能。
參考資料
- EAP 支援標準/Supported standards
- EAP 元件版本/Component details
- EAP 8.1 概念/Introduction
- EAP 8.1 遷移/Migration
- Boot 3.5 系統要求/Requirements
- Boot 3.5 依賴版本/Managed dependencies
- Boot 4.0 外部容器要求/Servlet requirements
- Spring Bean 與 IoC/Beans and IoC
- Spring MVC DispatcherServlet
- Boot WAR 部署/Traditional deployment
- Boot DataSource/JNDI
- Boot 設定來源/Externalized configuration
- Spring Security 遷移/Migration
- Hibernate 6.6 遷移/Migration
- IBM MQ Resource Adapters
- IBM MQ RAR 取得與命名/Obtaining adapters
- IBM MQ Adapter 支援/Support
- JDK 21 javax.sql
- JDK 21 javax.naming
- JDK 21 遷移/Migration
- Angular Update Guide