JBoss EAPSpring BootJava 21Jakarta EE系統升級

JBoss EAP × Spring Boot 升級入門:先懂執行路徑,再驗證行為不變

··閱讀約 14 分鐘
想先補 Spring Boot 基礎?從獨立的深入淺出系列開始接著學:JBoss EAP 獨立系列,基礎與實作

拿到一個舊 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 就自動變成同一套生命週期;跨邊界要有明確的整合方式。

text
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/EAPJDK 7/8;EAP 6.4JDK 21;EAP 8.1
Spring/Security3.2.9/4.3.x;3.x/4.1Framework 6.2.19;Security 6.5.11
Boot/Hibernate未使用 Boot;Hibernate 4.2/4.3Boot 3.5.16;Hibernate 6.6 系列
REST/建置CXF 2.6 JAX-RS;Ant/MavenSpring MVC 6.2;Maven
MQ/DB 驅動IBM MQ client 9.0.4;舊 JDBC jarMQ client 9.4.5.1;DB2 11.5 線相關驅動
前端Angular 11Angular 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 APIjavax.servlet、javax.persistence、javax.jms、javax.ejb遷移到對應 jakarta 命名空間及相容版本
Java SE APIjavax.sql、javax.naming、javax.crypto仍屬 JDK;不要全域取代
第三方/歷史 API舊 JAXB、驗證器、產生的 client code核對實際來源與相容版本,重新生成或逐項遷移
先分類,再修改 import、相依套件與部署描述檔。

JDK 21 仍有 javax.sql.DataSource 與 javax.naming.InitialContext。看到 javax 就替換,會把正確的 JDBC 與 JNDI 程式也改壞。只有 import 改完也不夠:舊 jar、XML schema、服務載入設定與反射使用的類別名稱,都可能仍指向舊 API。

bash
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 不一定是路由壞掉。

java
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 部署。

java
// 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 設定一定會生效。

properties
# Example only: this resource must already exist in EAP
spring.datasource.jndi-name=java:jboss/datasources/ExampleDS

Hibernate 與交易:查到資料,不代表提交方式沒變

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 回傳相同還不夠,前端仍需測登入、錯誤顯示、日期格式、路由與實際使用流程。

把「行為不變」寫成可以比較的證據

先在隔離的舊環境執行案例並保存原始輸出,再把相同初始資料、輸入與外部依賴條件帶到新環境。涉及副作用的案例需要各自還原資料快照,不能把同一筆付款或訊息對正式環境執行兩次。非同步處理須先定義觀察完成的條件與等待上限。

證據至少比較差異處理
HTTPstatus、header、schema、型別、null、時間/金額欄位與錯誤格式依契約比對
DB新增/修改/刪除、約束、提交與回滾排序僅用於沒有順序契約的結果
MQbody、業務屬性、數量、重新投遞、相關識別動態 ID 以關聯規則比較,不能一律忽略
Log關鍵事件、severity、錯誤、追蹤串接時間戳/thread 名稱依核准規則正規化
四種證據一起看;HTTP 200 不會涵蓋另外三欄。

保留原始證據與正規化後的結果。哪些欄位可忽略、哪些排序可調整,要事前寫進可版本控制的規則;AI 不應為了讓差異消失,自行刪掉失敗訊息或把所有數字遮掉。新 runtime 的啟動 log 不一定逐字相同,業務稽核事件卻可能需要嚴格對應。無法對應的差異列為未決,交由負責人判斷。

yaml
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/NoSuchMethodMaven 依賴、WAR 內容、EAP 模組與實際載入來源
找不到 DataSourceJNDI 名稱、EAP 設定、應用啟用的設定來源
MDB 沒收到或重複處理RAR、activation、selector、交易與重新投遞
批次完成但資料不對參數、資料快照、交易邊界、exit code 與重跑語意
這是排查起點,不是憑錯誤文字就自動套修正。

讓 AI 交接一個改動,而不是再交給你一疊文件

先挑一個 Web 端點或一個 MDB 案例,填完環境與行為契約,再請 AI 改一個邊界。交付時只要求:行為前後差異、設計依據、三個以內必看位置、驗證證據、未決事項。需要補理解時,問「這個例外會在哪裡讓交易回滾」,比「解釋整個 Spring」更容易對照實際程式。

下載升級盤點與行為比對工作表(Markdown)

今天可以先做的練習:選一個你負責的功能,用自己的話說出入口、容器、交易擁有者與四種證據。遇到無法回答的欄位,就只查那一段。等到能指出失敗時要回頭看哪份設定與哪筆紀錄,再擴大到下一個功能。

參考資料