用 AXIOM 促进 XML 处理
簡介:?AXis 對象模型(AXis Object Model,AXIOM)是 Apache Axis 2 的 XML 對象模型,其目標是提供強大的特性組合徹底改變 XML 處理技術。AXIOM 超越了現有的 XML 處理技術,它把延遲構建和一種快速、輕型的可定制對象模型結合了起來。本文中,軟件架構師、AXIOM 的首創者 Eran Chinthaka 介紹了這種新的 XML 處理方法。
AXIOM 還不是另一種對象模型。它有著明確的設計目標:大幅提升 Apache 下一代 SOAP 協議棧 Axis 2 的性能。結果造就了不同于其他對象模型的 AXIOM(也稱為 OM),因為它突出了構造的輕型,并且 僅當需要的時候才建立。由于是輕型的,它盡可能地減輕對系統資源的壓力,特別是 CPU 和內存。同時,延遲構造又允許在其他部分還沒有完成的時候使用樹的一部分。AXIOM 強大的延遲構建能力源于底層的 Streaming API for XML (StAX) 解析器。AXIOM 提供了所有這些特性,同時幕后的復雜性對用戶是透明的。
使用 XMLBench Document Model Benchmark 測試(請參閱 參考資料)的結果表明,AXIOM 的性能和現有的高性能對象模型相當。但是 AXIOM 的內存占用要好于現有多數依靠 SAX 和/或 DOM 輸入輸出的對象模型。因此對于 Web 服務引擎或內存受限制設備這樣的 XML 處理器,AXIOM 是一種理想的選擇,它可用于一般的 XML 處理,但是有一個對 SOAP 優化了的可選層。
使用 AXIOM在典型的 SOAP 引擎中,數據可能以三種不同的方法表示:
- 序列化形式,如 XML 或二進制 XML。
- 內存中基于樹的對象模型,如 DOM。
- 專用于特定語言的對象,如 Plain Old Java Object (POJO)。
比如一個 Web 服務的調用。傳遞給服務提供商的數據可能是用語言專用的對象,對于 Java 技術就是 POJO。調用過程的第一步是將這些對象中的信息項放入 SOAP 信封,構造一個 SOAP 消息。因為 SOAP 消息是 XML 文檔,所以 Web 服務還必須將數據項轉化成要求的 XML 格式。在內存中表示 XML Infoset 需要構造一個對象樹,供對象模型(AXIOM)使用。
從頭創建 AXIOM創建內存對象層次結構的第一步是創建一個對象工廠:
| OMFactory factory= OMAbstractFactory.getOMFactory(); |
?
AXIOM 允許很多不同的對象工廠實現,但鏈表是最常用的。一旦建立了工廠,就可以開始構造樹了。
比如下面的 XML 片段:
清單 1.Line item 細節
| <po:line-item po:quantity="2" xmlns:po="http://openuri.org/easypo"> <po:description> Burnham's Celestial Handbook, Vol 2 </po:description> <po:price>19.89</po:price></po:line-item> |
?
注意,所有的元素和屬性都屬于 "http://openuri.org/easypo" 名稱空間。因此,為這個 XML 片段構造 AXIOM 樹的第一步就是創建名稱空間,如下所示:
| OMNamespace poNs= factory.createOMNamespace("http://openuri.org/easypo", "po"); |
?
現在可以構造包裝器元素 line-item 了:
| OMElement lineItem= factory.createOMElement("line-item", poNs); |
?
接下來創建 line-item 元素相關的子元素和屬性。
最好用下面的方式創建元素屬性:
| lineItem.addAttribute("quantity", "2", poNs); |
?
與其他元素一樣創建子元素,然后按照下面的方式結合到父元素中:
| OMElement description= factory. createOMElement("description", poNs); description.setText("Burnham's Celestial Handbook, Vol 2"); lineItem.addChild(description); |
?
類似地,也添加 price 子元素:
| OMElement price= factory.createOMElement("price", poNs); price.setText("19.89"); lineItem.addChild(price); |
?
清單 2 顯示了完整的代碼片段。
清單 2.通過程序創建 line item
| OMFactory factory = OMAbstractFactory.getOMFactory(); OMNamespace poNs = factory.createOMNamespace("http://openuri.org/easypo", "po"); OMElement lineItem = factory.createOMElement("line-item", poNs); lineItem.addAttribute("quantity", "2", poNs); OMElement description = factory.createOMElement("description", poNs); description.setText("Burnham's Celestial Handbook, Vol 2"); lineItem.addChild(description); OMElement price = factory.createOMElement("price", poNs); price.setText("19.89"); lineItem.addChild(price); |
?
輸出現在可以使用 StAX writer 來序列化構造好的元素:
清單 3.序列化 line item
| XMLOutputFactory xof = XMLOutputFactory.newInstance(); XMLStreamWriter writer = xof. createXMLStreamWriter(System.out); lineItem.serialize(writer); writer.flush(); |
?
從已有代碼構造 AXIOM 現在看看相反的過程,從數據流建立內存對象模型。
最簡單的情況下,只需要關心 XML 片段的反序列化。但是在 SOAP 處理中,需要反序列化 SOAP 消息或者經過 MTOM 優化的 MIME 信封。因為與 SOAP 處理關系特別密切,所以 AXIOM 為此提供內置支持,稍候將詳細介紹。但首先要說明如何反序列化簡單的 XML 片段,具體來說就是剛剛序列化的那個 XML 片段。
首先構造一個解析器。AXIOM 支持用 SAX 和 StAX 解析器解析 XML。但是,SAX 解析不允許對象模型的延遲構造,因此在延遲構建很重要時,應該使用基于 StAX 的解析器。
第一步是為數據流獲得一個 XMLStreamReader:
| File file= new File("line-item.xml"); FileInputStream fis= new FileInputStream(file); XMLInputFactory xif= XMLInputFactory.newInstance(); XMLStreamReader reader= xif.createXMLStreamReader(fis); |
?
然后創建一個 builder 并將 XMLStreamReader 傳遞給它:
| StAXOMBuilder builder= new StAXOMBuilder(reader); lineItem= builder.getDocumentElement(); |
?
現在可以使用 AXIOM API 來訪問屬性和子元素或者 XML Infoset 項了。可以這樣訪問屬性:
| OMAttribute quantity= lineItem.getFirstAttribute( new QName("http://openuri.org/easypo", "quantity")); System.out.println("quantity= " + quantity.getValue()); |
?
用類似的方式訪問子元素:
| price= lineItem.getFirstChildWithName( new QName("http://openuri.org/easypo", "price")); System.out.println("price= " + price.getText()); |
?
清單 4 顯示了完整的代碼片段。
清單 4.從 XML 文件構建 AXIOM
| File file = new File("line-item.xml"); FileInputStream fis = new FileInputStream(file); XMLInputFactory xif = XMLInputFactory.newInstance(); XMLStreamReader reader = xif.createXMLStreamReader(fis); StAXOMBuilder builder = new StAXOMBuilder(reader); OMElement lineItem = builder.getDocumentElement(); lineItem.serializeWithCache(writer); writer.flush(); OMAttribute quantity = lineItem.getFirstAttribute( new QName("http://openuri.org/easypo", "quantity")); System.out.println("quantity= " + quantity.getValue()); OMElement price = lineItem.getFirstChildWithName( new QName("http://openuri.org/easypo", "price")); System.out.println("price= " + price.getText()); OMElement description = lineItem.getFirstChildWithName(new QName("http://openuri.org/easypo", "description")); System.out.println("description= " + description.getText()); |
?
AXIOM 最好的一點是,努力在延遲構造這類高端技術上提供用戶友好的 API。但是要充分發揮其潛能,必須了解底層體系結構。
回頁首
進一步考察 AXIOM
緩沖是 AXIOM 的核心概念之一。但是,要理解緩沖必須在樹的延遲構造和 AXIOM API 上下文中來思考。AXIOM 提供多種訪問底層 XML Infoset 的 API。上面使用的是基于樹的 API,所有其他競爭的對象模型如 DOM 和 JDOM 都提供了這樣的 API。但是,AXIOM 還允許通過 SAX 或 StAX API 訪問信息。如圖 1 所示。
圖 1. AXIOM,輸入和輸出
如果要使用一種 XML 解析 API,為何還要構造對象模型呢?為了使用不同 API 訪問對象模型的不同部分。比如,考慮 SOAP 棧的情況:SOAP 消息在被目標服務消費之前可能會經過多個處理程序的處理。這些處理程序通常使用基于樹的 API(特別是 SOAP with Attachments API for Java,或 SAAJ)。服務實現還可能使用數據綁定工具將 SOAP 消息負荷中的 XML 文檔轉化成對象,如 POJO。因為用戶不使用基于樹的對象模型來訪問這部分文檔,所以構造完整的樹會因為數據重復而浪費內存。最直接的解決方法是向數據綁定工具公開底層的原始 XML 流。這就是 AXIOM 的閃光之處。
為了獲得最佳的性能和內存使用,需要讓數據綁定工具直接訪問底層的 XML 流。AXIOM 完全允許這樣做。延遲構建僅僅意味著只有在訪問的時候才構造要訪問的這部分樹。因此如果不需要訪問 SOAP 消息體,SOAP 消息的這部分就不會被構建。如果用戶開始使用 SAX 或 StAX 訪問消息體,而它還沒有構建,AXIOM 將把用戶直接連接到底層的解析器,以便提供最佳的性能。如圖 2 所示:
圖 2.通過 AXIOM 訪問底層的解析器
但是,如果用戶希望再回來訪問樹的同一部分就可能出現問題。因為解析器已經直接連接了用戶,AXIOM 退出了,就是說所有信息都從低層的流直接流向用戶。因此當用戶回來請求同樣的信息時,無論第二次選擇什么樣的 API,AXIOM 都不能提供該信息。注意這兩種可能性差不多相等。比如,多數情況下 SOAP 體的處理中只有最終的服務實現才會涉及到負荷。服務可以使用數據綁定或其他 XML 處理 API 如 SAX、StAX 或 XPath 來處理消息體。這種情況下,消息體很少被訪問兩次,AXIOM 提供的優化具有最好的性能。
但是,假設在處理程序鏈中插入一個日志處理程序,使用 StAX writer 記錄整個 SOAP 消息。如果服務實現嘗試訪問消息體,而消息體不存在!
為了進一步說明這一點,下面是一個比較簡單的例子,雖然有點牽強。
| StAXOMBuilder builder = new StAXOMBuilder(reader); lineItem = builder.getDocumentElement(); lineItem.serialize(writer); writer.flush(); price = lineItem.getFirstChildWithName( new QName("http://openuri.org/easypo", "price")); System.out.println("price= " + price.getText()); |
?
由于延遲構造,獲得 lineItem 元素的時候該元素還沒有構造完成。因此后面使用 StAX writer 進行序列化時,AXIOM 把 StAX writer(它序列化 lineItem 元素)直接連接到 StAX reader(它最初被傳遞給 builder)。但是這個過程中,AXIOM 斷開了自身和數據流的連接。現在當請求 price 子元素的時候,找不到這樣的元素,因為 lineItem 的所有子元素都在序列化器中消失了。
這種情況下,惟一的辦法是避免序列化過程中 AXIOM 完全和數據流脫離開。用 AXIOM 的術語稱為緩沖:無論是否在內存中建立了對象模型,AXIOM 都允許獲得 StAX 事件或者 序列化 XML。因此,AXIOM 把策略(比如是否應該緩沖消息)和機制(如何緩沖)分離開來。它允許用戶在開始使用原始 XML 處理 API(如 SAX 或 StAX)時決定是否緩沖樹中未用到的部分以供將來引用。如果用戶決定這樣做,當樹構造完成時可以再回來訪問這些部分。但是,用戶必須付出內存占用和性能的代價。另一方面,如果用戶了解自己的目標,并確信只此一次需要訪問樹的這些部分,則可以選擇關閉 緩沖來充分發揮 AXIOM 的效率。
因此,上一段代碼應改寫為:
| StAXOMBuilder builder = new StAXOMBuilder(reader); lineItem = builder.getDocumentElement(); lineItem.serializeWithCache(writer); writer.flush(); price = lineItem.getFirstChildWithName( new QName("http://openuri.org/easypo", "price")); System.out.println("price= " + price.getText()); |
?
方法 serializeWithCache 與對應的 serialize 不同,不會將 StAX reader 直接連接到 StAX writer。相反,從 reader 傳遞給 writer 的所有數據都保留 在 AXIOM 中。具體如何緩沖與用戶無關。目前如果啟用緩沖,AXIOM 就會像用戶在通過文檔 API 訪問樹的這些部分一樣構造樹。
回頁首
AXIOM 和 StAX
了解這些背景之后,現在看看 AXIO 的 StAX API。該 API 中最重要的方法如下:
| (OMElement).getXMLStreamReader(); (OMElement).getXMLStreamReaderWithoutCaching(); |
?
通過 StAX API 對某個元素調用第一個方法,可以訪問該元素的 XML Infoset,同時緩沖(如果需要)樹中未構造的部分以供將來使用。顧名思義,第二個方法用于訪問同樣的信息,但是通過關閉緩沖機制優化了性能。在編寫需要使用數據綁定框架的存根和 skeleton 程序時,這是最有用的方法。
但是請注意,如果在調用上述方法之前已經建立了樹,AXIOM 將模擬 StAX 解析器。因此有些樹節點的事件是通過模擬而來的,而對于另一些節點則直接連接到底層的解析器。AXIOM 的優點在于這些內部處理對用戶是透明的。但是,在切換到原始 API 時,必須指明是否需要緩沖數據。
為了說明 StAX API 的用法,我將展示如何使用 XMLBeans 生成的代碼連接到 AXIOM。
清單 5.XMLBeans 生成的訂單代碼
| public class PurchaseOrderSkel { public void submitPurchaseOrder(PurchaseOrderDocument doc) throws Exception { } public void submitPurchaseOrderWrapper( OMElement payload) { try { XMLStreamReader reader= payload. getXMLStreamReaderWithoutCaching(); PurchaseOrderDocument doc = PurchaseOrderDocument.Factory.parse(reader); submitPurchaseOrder(doc); } catch (Exception ex) { ex.printStacktrace(); } } } |
?
清單 5 中的代碼(通常用代碼生成工具生成)展示了一個 skeleton,它使用 XMLBeans 生成的類(即 PurchaseOrderDocument)進行數據綁定。這個 skeleton 包含兩個服務實現方法。第一個允許服務實現者使用數據綁定對象,第二個則允許直接訪問 AXIOM API。主要看看這幾行:
| XMLStreamReader reader= payload. getXMLStreamReaderWithoutCaching(); PurchaseOrderDocument doc = PurchaseOrderDocument.Factory.parse(reader); |
?
為了創建對象,首先對 SOAP 棧(如 Apache Axis)壓入服務實現的載荷獲得對 StAX API 的引用。因為現在在處理鏈的最末端,所以可以安全地把解析器直接連接到 XMLBeans 解除封送器以獲得最佳性能。
對于 清單 5 中的 skeleton,其存根代碼類似于 清單 6。
清單 6.存根代碼
| public class PurchaseOrderStub { public void submitPurchaseOrder( PurchaseOrderDocument doc) throws Exception { SOAPEnvelope envelope = factory.getDefaultEnvelope(); XMLStreamReader reader = doc.newXMLStreamReader(); StAXOMBuilder builder = new StAXOMBuilder(reader); OMElement payload= builder.getDocumentElement(); envelope.getBody().addChild(payload); // ... } } |
?
主要看看這幾行:
| XMLStreamReader reader = doc.newXMLStreamReader(); StAXOMBuilder builder = new StAXOMBuilder(reader); Element payload= builder.getDocumentElement(); |
?
從這段代碼可以看出,經過 StAX API 從對象到 AXIOM,與從 XML 到 AXIOM 沒有什么區別。
但是初看起來不那么明顯的是延遲構造仍然在起作用!即使在將載荷插入 SOAP 信封的過程中創建了 OMElement,內存中也沒有重復的信息項。這是由于延遲構造和 AXIOM 內的多路技術造成的,它將從一個 API 輸入的數據直接轉發給另一個 API 輸出。當消息最終寫入流的時候,XMLBeans 提供的 XMLStreamReader 直接連接到傳輸 writer,后者將消息寫入套接字 —— 假設此過程中沒有要查看消息的處理程序。這意味著直到此時,數據仍然存放在 XMLBeans 對象中,真是好極了!
回頁首
AXIOM 和數據綁定
這里討論 AXIOM 的 SAX API,因為有些數據綁定框架不能使用其他的 API,比如 JAXB。雖然上述情況下使用 SAX 顯然不會達到最佳性能,但從 AXIOM 到對象使用 SAX 并沒有造成性能損失,因為這一步在任何情況下都是必需的。
如果使用 JAXB,那么存根程序就要使用 SAXOMBuilder 從數據綁定對象建立 AXIOM。清單 7 示范了這個過程。
清單 7. AXIOM 和 JAXB
| public class PurchaseOrderStub { public void submitPurchaseOrder( PurchaseOrder doc) throws Exception { SOAPEnvelope envelope = factory.getDefaultEnvelope(); SAXOMBuilder builder = new SAXOMBuilder(); JAXBContext jaxbContext = JAXBContext.newInstance("po"); Marshaller marshaller = jaxbContext.createMarshaller(); marshaller.marshal(doc, builder); OMElement payload= builder.getDocumentElement(); envelope.getBody().addChild(payload); //... } } |
?
到目前為止,AXIOM 還不允許使用 OMElement 注冊內容處理程序來處理收到的 SAX 事件。不過很容易編寫一段膠水代碼,從提供的 StAX 接口接收事件并驅動 SAX ContentHandler。有興趣的讀者可以從 參考資料 中的 JAXB 參考實現中找到這樣的實現。
回頁首
結束語
我介紹了與典型的 XML 對象模型相比 AXIOM 引入的一些很有前途的特性。注意本文僅僅介紹了部分特性。AXIOM 有很多更強大的特性,建議您從 Axis 2 源代碼庫(請參閱 參考資料)下載最新的源代碼,進一步研究 AXIOM。
轉載于:https://www.cnblogs.com/chinacloud/archive/2010/10/28/1863168.html
總結
以上是生活随笔為你收集整理的用 AXIOM 促进 XML 处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 换了工作城市,社保和公积金的转移
- 下一篇: asp.net ajax控件工具集 Au