Apache Axis2 1.1 版本已發布,為長期使用 Apache Web 服務框架的開發者帶來令人振奮的新特性。本系列文章將深入探討 Axis2 的核心創新——AXIOM (AXis Object Model) XML 文檔模型。AXIOM 是 Axis2 性能優於前代 Axis 的關鍵原因之一。本文將介紹 AXIOM 的運作原理、Axis2 各組件如何建構於 AXIOM 之上,以及 AXIOM 與其他 Java 文檔對象模型的性能對比。
處理 XML 時,Java 開發者可選擇多種文檔模型:W3C DOM 各種實現、JDOM、dom4j、XOM 等。各模型在性能、靈活性、標準遵循度上各有優劣。但 Axis2 為何需要新模型?答案在於 SOAP 訊息結構,特別是 SOAP Header 的擴展機制。
SOAP 本質上是 XML 應用負載的簡單包裝。範例 1 展示典型 SOAP 訊息,只有 soapenv 前綴元素屬於 SOAP 定義,大部分內容為應用數據,置於 soapenv:Body 中。
<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header/>
<soapenv:Body>
<matchQuakes xmlns="https://seismic.sosnoski.com/types">
<min-date>2001-01-06T11:10:43.446Z</min-date>
<max-date>2001-10-24T19:49:13.812Z</max-date>
<min-long>-150.94307</min-long>
<max-long>-22.594208</max-long>
<min-lat>-11.44651</min-lat>
<max-lat>55.089058</max-lat>
</matchQuakes>
</soapenv:Body>
</soapenv:Envelope>
SOAP 透過可選的 Header 提供無限制擴展能力。Header 可攜帶各類元數據,不影響應用數據處理。WS-* 系列標準即利用 Header 實現功能,如範例 2 所示的 WS-Addressing 資訊,使訊息能跨協議傳輸、支援存儲轉發。
<soapenv:Envelope xmlns:soapenv="https://schemas.xmlsoap.org/soap/envelope/"
xmlns:wsa="https://www.w3.org/2005/08/addressing">
<soapenv:Header>
<wsa:To>https://localhost:8800/axis2/services/SeisAxis2XBean</wsa:To>
<wsa:ReplyTo>
<wsa:Address>https://www.w3.org/2005/08/addressing/anonymous</wsa:Address>
</wsa:ReplyTo>
<wsa:MessageID>urn:uuid:97AE2B17231A8584D811537402403691</wsa:MessageID>
</soapenv:Header>
<soapenv:Body>...</soapenv:Body>
</soapenv:Envelope>
SOAP Header 允許任意元數據,框架必須能處理任意 XML,文檔模型恰擅長此道。但對於應用數據交換,數據綁定(Data Binding)將 XML 轉為數據對象,更方便、高效。理想方案是同時使用兩種技術:Body 用數據綁定,Header 用文檔模型。傳統文檔模型卻要求處理整個文檔或完整子樹,無法只處理文檔選定部分。
Axis2 另一大改進是採用拉式 StAX 解析器,取代 Axis 的推式 SAX 解析器。推式由解析器控制流程,拉式則由應用程式主動拉取所需部分。對於 SOAP 這類包含邏輯獨立部分的 XML,拉式更具優勢:處理某部分代碼只解析該部分,其餘交由解析器續處理。
AXIOM 建構於 StAX 之上,提供按需擴展的虛擬文檔模型。僅構建客戶端請求的樹結構表示。元素級運作:解析器報告元素開始標記時創建元素表示,初始僅為保存解析器引用的殼。應用程式需獲取內容細節時,透過 OMContainer.getChildren() 等方法請求,解析器才構建子內容。因解析器按文檔順序傳遞數據,按需構造要求靈活處理:多個不完整元素始終位於樹右側直線上,隨請求逐漸展開。
AXIOM API 與其他文檔模型有相似之處,但因按需構建特性而獨特。訪問方法基於 java.util.Iterator,導航用 OMNode.getNextOMSibling()、getPreviousOMSibling(),符合按需構建邏輯:可移至第一個子元素而無需先處理所有子元素。
AXIOM 使用接口定義 API,提供多種實現:org.apache.axiom.om.impl.dom 同時支援 AXIOM 和 DOM 接口;org.apache.axiom.om.impl.llom 基於鏈表實現;org.apache.axiom.soap 專門處理 SOAP 訊息。
範例 3 展示解析輸入流為 AXIOM 文檔:
import org.apache.axiom.om.*;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axiom.om.impl.llom.factory.OMLinkedListImplFactory;
...
private XMLInputFactory m_parserFactory = XMLInputFactory.newInstance();
private OMFactory m_factory = new OMLinkedListImplFactory();
...
protected Object build(InputStream in) {
Object doc = null;
try {
XMLStreamReader reader = m_parserFactory.createXMLStreamReader(in);
StAXOMBuilder builder = new StAXOMBuilder(m_factory, reader);
doc = builder.getDocument();
} catch (Exception ex) { ... }
return doc;
}
代碼僅創建 StAX 解析器和基本文檔結構,解析器定位於根元素,後續按需構建。也可用 SAXOMBuilder 基於 SAX 構建,但無法享受按需構造優勢。
範例 4 遍歷文檔累計摘要資訊(元素計數、屬性值長度、文本內容長度):
protected void walkElement(OMElement element, DocumentSummary summary) {
for (Iterator iter = element.getAllAttributes(); iter.hasNext();) {
OMAttribute attr = (OMAttribute)iter.next();
summary.addAttribute(attr.getAttributeValue().length());
}
for (Iterator iter = element.getChildren(); iter.hasNext();) {
OMNode child = (OMNode)iter.next();
int type = child.getType();
if (type == OMNode.TEXT_NODE) {
summary.addContent(((OMText)child).getText().length());
} else if (type == OMNode.ELEMENT_NODE) {
summary.addElements(1);
walkElement((OMElement)child, summary);
}
}
}
protected void walk(Object doc, DocumentSummary summary) {
summary.addElements(1);
walkElement(((OMDocument)doc).getOMDocumentElement(), summary);
}
範例 5 將文檔序列化輸出:
protected void output(Object doc, OutputStream out) {
try {
((OMDocument)doc).serializeAndConsume(out);
} catch (Exception ex) { ... }
}
AXIOM 提供修改文檔基本方法(如 OMElement.setText()),從頭創建文檔則透過工廠創建組件實例。
AXIOM 內建支援 W3C XOP 和 MTOM 標準,用於 SOAP 附件傳輸。XOP 以 Base64 編碼二進制數據,透過 xop:Include 元素引用外部實體(URI),減小文檔體積、避免編解碼開銷。MTOM 將 XOP 應用於 SOAP,定義 MIME Multipart/Related 封裝,支援 HTTP 傳輸。
AXIOM 透過 OMText 介面及實現支援 XOP/MTOM。OMText 以 DataHandler 表示二進制數據,含「優化」旗標指示是否用 XOP 處理。OMTextImpl 添加 MTOM 相容內容 ID。
範例 6 創建 XOP/MTOM 訊息:
public OMElement matchQuakes(OMElement req) {
Query query = new Query();
Iterator iter = req.getChildElements();
try {
Response response = QuakeBase.getInstance().handleQuery(query);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(response);
byte[] byts = bos.toByteArray();
ByteArrayDataSource ds = new ByteArrayDataSource(byts);
OMFactory fac = OMAbstractFactory.getOMFactory();
OMNamespace ns = fac.createOMNamespace("https://seismic.sosnoski.com/types", "qk");
OMElement resp = fac.createOMElement("response", ns);
OMText data = fac.createOMText(new DataHandler(ds), true);
resp.addChild(data);
return resp;
} catch (Exception e) { ... }
return null;
}
需在 Axis2 配置中啟用 <parameter name="enableMTOM">true</parameter>。範例 7 展示啟用與未啟用 XOP/MTOM 的回應差異。
開發者習慣用 Java 對象而非 XML。Axis2 重新設計支援多種數據綁定框架作為「插件」。透過擴展 WSDL2Java 工具,生成存根代碼在 OMElement 和 Java 對象間轉換。入站時,XML 負載以 OMElement 形式獲取,數據綁定框架處理元素數據(可直接用 XMLStreamReader)。出站時,AXIOM 用 OMDataSource 包裝數據綁定轉換,按需提供數據,避免構建完整 XML 表示。
測試環境:Compaq 筆記本,1.6GHz AMD Turion,1.5GB RAM,Mandriva Linux,Sun JVM 1.5.0_07。對比 AXIOM 1.0、dom4j 1.6.1、JDOM 1.0、Xerces2 2.7.0、XOM (Nux 1.6),均用 Woodstox StAX 2.9.3 解析器。測試三種 SOAP 文檔:「quakes」(18KB)、「persons」(202KB)、「soaps」(19KB,30個小訊息)。

圖1:構建並完整展開文檔的平均時間。除 Xerces2 外,AXIOM 比其他模型略慢。

圖2:寫入輸出流平均時間。Xerces2 最快,AXIOM 最差,尤其小文檔。

圖3:內存佔用。dom4j 最優,AXIOM 明顯較大,部分因保存解析器引用,部分因對象本身較大。

圖4:初始構建時間(不展開)。AXIOM 顯著更快,大文檔甚至難以測量。若只需處理文檔部分(如按順序的首部),AXIOM 性能優異。
AXIOM 的按需構建思想創新,完整展開時性能不如傳統模型(尤其小文檔),但針對 Web 服務處理靈活性足以彌補。下一篇將探討 Axis2 如何支援不同數據綁定框架。