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