Web 服務 (SOAP/HTTP) 成為企業整合標準,但傳統 HTTP 存取日誌無法反映「使用者感知的端到端回應時間」:單一業務交易包含多次 SOAP 呼叫,且網路傳輸、客戶端處理耗時皆在伺服器端不可見。本文提出客戶端主動測量 + SOAP 附件攜帶回報的輕量級架構,實現真實 QoS 可視化。

| 層級 | 組件 | 職責 |
|---|---|---|
| 客戶端應用層 | 業務邏輯 + Payload API | `beginTransaction()` / `commitTransaction()` 記錄時間戳 |
| 客戶端 Web 服務層 | Stub + Payload 序列化 | 將度量資料序列化為 SOAP Header/Attachment |
| 伺服器端 Web 服務層 | SOAP Handler / Interceptor | 剝離附件、解析度量、放入非同步佇列 |
| EJB 持久層 | Metrics MDB (Message-Driven Bean) | 消費佇列、寫入資料庫、離線分析 |
public final class Payload {
private static final ThreadLocal tx = new ThreadLocal<>();
public static void beginTransaction(String businessTxId) {
Transaction t = new Transaction();
t.businessTxId = businessTxId;
t.clientStart = System.nanoTime();
tx.set(t);
}
public static void commitTransaction() {
Transaction t = tx.get();
if (t == null) return;
t.clientEnd = System.nanoTime();
t.clientElapsedMs = (t.clientEnd - t.clientStart) / 1_000_000;
// 序列化為 SOAP Attachment (由 Handler 自動攜帶)
tx.remove();
}
// 供 Client Handler 讀取
static Transaction getCurrent() { return tx.get(); }
static class Transaction implements Serializable {
String businessTxId;
long clientStart, clientEnd;
long clientElapsedMs;
}
}
public class MetricsClientHandler extends GenericHandler {
public QName[] getHeaders() { return new QName[0]; }
public boolean handleRequest(MessageContext ctx) {
SOAPMessageContext smc = (SOAPMessageContext) ctx;
try {
SOAPMessage msg = smc.getMessage();
Transaction tx = Payload.getCurrent();
if (tx != null) {
// 建立 AttachmentPart
AttachmentPart part = msg.createAttachmentPart();
part.setContent(tx, "application/x-java-serialized-object");
part.setContentId("");
msg.addAttachmentPart(part);
msg.saveChanges();
}
} catch (Exception e) {
// 靜默失敗,記錄本地日誌
log.warn("Metrics attachment failed", e);
}
return true;
}
}
SOAPHandler.handleRequest() → 遍歷 `msg.getAttachments()` 找 `Content-Type: application/x-java-serialized-object`。@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/metricsQueue")
})
public class MetricsMDB implements MessageListener {
@Resource DataSource ds;
public void onMessage(Message msg) {
try (Connection c = ds.getConnection()) {
ObjectMessage om = (ObjectMessage) msg;
Transaction tx = (Transaction) om.getObject();
// 寫入度量表
try (PreparedStatement ps = c.prepareStatement(
"INSERT INTO qos_metrics (biz_tx_id, client_elapsed_ms, server_received_ts) VALUES (?, ?, ?)")) {
ps.setString(1, tx.businessTxId);
ps.setLong(2, tx.clientElapsedMs);
ps.setTimestamp(3, new Timestamp(System.currentTimeMillis()));
ps.executeUpdate();
}
} catch (Exception e) {
// 重試邏輯:重發至 DLQ 或寫本地檔案
log.error("Persist metrics failed", e);
}
}
}
下篇將詳細實作:JAX-WS 2.x HandlerChain 整合、Spring Boot Actuator 指標暴露、Grafana 儀表板模板、異常鏈路追蹤關聯 (OpenTelemetry/W3C TraceContext)。