原帖地址:http://www.ibm.com/developerworks/cn/xml/x-stax1.html
2007 年 3 月 02 日
Streaming API for XML (StAX) 是用 Java™ 語言處理 XML 的最新標準。作為一種面向流的方法,無論從性能還是可用性上都優(yōu)于其他方法,如 DOM 和 SAX。本系列分為 3 部分,本文是第 1 部分,簡要介紹了 StAX 及其處理 XML 的基于指針的 API。
從一開始,Java API for XML Processing (JAXP) 就提供了兩種方法來處理 XML:文檔對象模型(DOM)方法是用標準的對象模型表示 XML 文檔;Simple API for XML (SAX) 方法使用應用程序提供的事件處理程序來處理 XML。JSR-173 提出了一種面向流的新方法:Streaming API for XML (StAX)。其最終版本于 2004 年 3 月發(fā)布,并成為了 JAXP 1.4(將包含在即將發(fā)布的 Java 6 中)的一部分。
如其名稱所暗示的那樣,StAX 把重點放在流上。實際上,StAX 與其他方法的區(qū)別就在于應用程序能夠把 XML 作為一個事件流來處理。將 XML 作為一組事件來處理的想法并不新穎(事實上 SAX 已經提出來了),但不同之處在于 StAX 允許應用程序代碼把這些事件逐個拉出來,而不用提供在解析器方便時從解析器中接收事件的處理程序。
StAX 實際上包括兩套處理 XML 的 API,分別提供了不同程度的抽象。基于指針的 API 允許應用程序把 XML 作為一個標記(或事件)流來處理;應用程序可以檢查解析器的狀態(tài),獲得解析的上一個標記的信息,然后再處理下一個標記,依此類推。這是一種低層 API,盡管效率高,但是沒有提供底層 XML 結構的抽象。較為高級的基于迭代器的 API 允許應用程序把 XML 作為一系列事件對象來處理,每個對象和應用程序交換 XML 結構的一部分。應用程序只需要確定解析事件的類型,將其轉換成對應的具體類型,然后利用其方法獲得屬于該事件的信息。
![]() ![]() |
![]()
|
為了使用這兩類 API,應用程序首先必須獲得一個具體的 XMLInputFactory
。根據傳統(tǒng)的 JAXP 風格,要用到抽象工廠模式;XMLInputFactory
類提供了靜態(tài)的 newInstance
方法,它負責定位和實例化具體的工廠。配置該實例可設置定制或者預先定義好的屬性(其名稱在類 XMLInputFactory 中定義)。最后,為了使用基于指針的 API,應用程序還要通過調用某個 createXMLStreamReader
方法獲得一個 XMLStreamReader
。如果要使用基于事件迭代器的 API,應用程序就要調用 createXMLEventReader
方法獲得一個 XMLEventReader
(如清單 1 所示)。
清單 1. 獲取和配置默認的
XMLInputFactory
// get the default factory instance XMLInputFactory factory = XMLInputFactory.newInstance(); // configure it to create readers that coalesce adjacent character sections factory.setProperty(XMLInputFactory.IS_COALESCING, Boolean.TRUE); XMLStreamReader r = factory.createXMLStreamReader(input); // ... |
XMLStreamReader
和 XMLEventReader
都允許應用程序迭代底層的 XML 流。兩種方法的差別在于如何公開解析后的 XML InfoSet 信息片段。XMLStreamReader
就像一個指針,指在剛剛解析過的 XML 標記的后面,并提供了方法獲得更多關于該標記的信息。這種方法節(jié)約內存,因為不用創(chuàng)建新的對象。但是,業(yè)務應用程序開發(fā)人員可能會發(fā)現 XMLEventReader
更直觀一些,因為它實際上就是一個標準的 Java 迭代器,將 XML 變成了事件對象流。每個事件對象都封裝了它所表示的特定 XML 結構固有的信息。本系列的第二部分將詳細討論這種基于事件迭代器的 API。
使用哪種風格的 API 取決于具體情況。和基于指針的 API 相比,基于事件迭代器的 API 具有更多的面向對象特征。因此更便于應用于模塊化的體系結構,因為當前的解析器狀態(tài)反映在事件對象中,應用程序組件在處理事件的時候不需要訪問解析器/讀取器。此外,還可以使用 XMLInputFactory
的 createXMLEventReader(XMLStreamReader)
方法從 XMLStreamReader
創(chuàng)建 XMLEventReader
。
StAX 還定義了一種序列化器 API,Java 標準 XML 處理支持中一直缺少的一種特性。和解析一樣,也包含兩種風格的流式 API:處理標記的底層 XMLStreamWriter
和處理事件對象的高層 XMLEventWriter
。XMLStreamWriter
提供了寫入單個 XML 記號(比如開始和關閉標記或者元素屬性)的方法,不檢查這些標記是否格式良好。另一方面,XMLEventWriter
允許應用程序向輸出中添加完整的 XML 事件。第 3 部分將詳細討論 StAX 序列化器 API。
開始學習一種新的處理 XML 的 API 之前,可能要問是否值得這樣做。事實上,StAX 所采用的基于拉的方法和其他方法相比有一些突出的優(yōu)點。首先,不管使用哪種 API 風格,都是應用程序調用讀取器(解析器)而不是相反。通過保留解析過程的控制權,可以簡化調用代碼來準確地處理它預期的內容。或者發(fā)生意外時停止解析。此外,由于該方法不基于處理程序回調,應用程序不需要像使用 SAX 那樣模擬解析器的狀態(tài)。
StAX 仍然保留了 SAX 相對于 DOM 的優(yōu)點。通過把重心從結果對象模型轉移到解析流本身,從理論上說應用程序能夠處理無限的 XML 流,因為事件固有的臨時性,不會在內存中累積起來。對于那些使用 XML 作為消息傳遞協(xié)議而非表示文檔內容的那些應用程序尤其重要,比如 Web 服務或即時消息應用程序。比方說,如果只是將其轉換成特定于應用程序的對象模型然后就將其丟棄,那么為 Web 服務路由器 servlet 提供一個 DOM 就沒有多少用處。使用 StAX 直接轉化成應用程序模型效率更高。對于 Extensible Messaging and Presence Protocol(XMPP)客戶機,根本不能使用 DOM,因為 XMPP 客戶機/服務器流是隨著用戶輸入的消息實時生成。等待流的關閉標簽(以便最終建立 DOM)就意味著等待整個會話結束。通過把 XML 作為一系列的事件來處理,應用程序能夠以最合適的方式響應每個事件(比如顯示收到的即時消息等等)。
由于其雙向性,StAX 也支持鏈式處理,特別是在事件層上。接收事件(無論什么來源)的能力被封裝在 XMLEventConsumer(XMLEventWriter 的擴展)接口中。因此,可以模塊化地編寫應用程序從 XMLEventReader(也是一個普通的迭代器,可以按迭代器處理)讀取和處理 XML 事件、然后傳遞給事件消費者(如果需要可以進一步擴展處理鏈)。在第 2 部分將看到,也可使用應用程序提供的篩選器(實現了 EventFilter 接口的類)來定制 XMLEventReader 或者使用 EventReaderDelegate 修飾已有的 XMLEventReader。
總而言之,和 DOM 以及 SAX 相比,StAX 使應用程序更貼近底層的 XML。使用 StAX,應用程序不僅可以建立需要的對象模型(而不需要處理標準 DOM),而且可以隨時這樣做,而不必等到解析器回調。
下一節(jié)將深入討論基于指針的 API 以及如何有效地使用它處理 XML 流。
![]() ![]() |
![]()
|
如果使用基于指針的 API,應用程序通過在 XML 標記流中移動邏輯指針來處理 XML。基于指針的解析器實質上是一個狀態(tài)機,在事件的驅動下從一個良好定義的狀態(tài)轉移到另一個狀態(tài)。這里的觸發(fā)事件是隨著應用程序使用適當的方法推動解析器在標記流中前進而解析出來的 XML 標記。在每個狀態(tài),都可使用一組方法獲得上一個事件的信息。一般來說,并非每個狀態(tài)下都能使用所有的方法。
使用基于指針的方法,應用程序首先必須通過調用其 createXMLStreamReader
方法從 XMLInputFactory
得到 XMLStreamReader
。該方法有多個版本,支持不同類型的輸入。比方說,可以創(chuàng)建 XMLStreamReader
解析 plain java.io.InputStream
、java.io.Reader
或者 JAXP Source(javax.xml.transform.Source
)。從理論上說,后一種辦法很容易和其他 JAXP 技術交互,比如 SAX 和 DOM。
清單 2. 創(chuàng)建
XMLStreamReader
解析 InputStream
URL url = new URL(uri); InputStream input = url.openStream(); XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader r = factory.createXMLStreamReader(uri, input); // process the stream // ... r.close(); input.close(); |
XMLStreamReader
接口基本上定義了基于指針的 API(雖然標記常量在其超類型 XMLStreamConstants
接口中定義)。之所以稱為基于指針,是因為讀取器就像是底層標記流上的指針。應用程序可以沿著標記流向前推進指針并分析當前指針所在位置的標記。
XMLStreamReader
提供了多種方法導航標記流。為了確定當前指針所指向的標記(或事件)的類型,應用程序可以調用 getEventType()
。該方法返回接口 XMLStreamConstants
中定義的一個標記常量。移動到下一個標記,應用程序可以調用 next()
。該方法也返回解析的標記的類型,如果接著調用 getEventType()
則返回的值相同。只有當方法 hasNext()
返回 true 時(就是說還有其他標記需要解析)才能調用該方法(以及其他移動讀取器的方法)。
清單 3. 使用
XMLStreamReader
處理 XML 的常用模式
// create an XMLStreamReader XMLStreamReader r = ...; try { int event = r.getEventType(); while (true) { switch (event) { case XMLStreamConstants.START_DOCUMENT: // add cases for each event of interest // ... } if (!r.hasNext()) break; event = r.next(); } } finally { r.close(); } |
還與其他幾種方法可以移動 reader
。 nextTag()
方法將跳過所有的空白、注釋或處理指令,直到遇到 START_ELEMENT
或 END_ELEMENT
。該方法在解析只含元素的內容時很有用,如果在發(fā)現標記之前遇到非空白文本(不包括注釋或處理指令),就會拋出異常。getElementText()
方法返回元素的開始和關閉標簽(即 START_ELEMENT
和 END_ELEMENT
)之間的所有文本內容。如果遇到嵌套的元素就會拋出異常。
請注意,這里的 “標記” 和 “事件” 可以互換使用。雖然基于指針的 API 的文檔說的是事件,但把輸入源看成標記流很方便。而且不容易造成混亂,因為還有一整套基于事件的 API(那里的事件是真正的對象)。不過,XMLStreamReader
的事件本質上并非都是標記。比方說,START_DOCUMENT
和 END_DOCUMENT
事件不需要對應的標記。前一個事件是解析開始之前發(fā)生,后者則在沒有更多解析工作要做的時候發(fā)生(比如解析完成最后一個元素的關閉標簽之后,讀取器處于 END_ELEMENT
狀態(tài),但是如果沒有發(fā)現更多的標記需要解析,讀取器就會切換到 END_DOCUMENT
狀態(tài))。
在每個解析器狀態(tài),應用程序都可通過可用的方法獲得相關信息。比如,無論當前是什么類型的事件,getNamespaceContext()
和 getNamespaceURI()
方法可以獲得當前有效的名稱空間上下文和名稱空間 URI。類似的,getLocation()
可以獲得當前事件的位置信息。方法 hasName()
和 hasText()
可以分別判斷當前事件是否有名稱(比如元素或屬性)或文本(比如字符、注釋或 CDATA)。方法 isStartElement()
、isEndElement()
、isCharacters()
和 isWhiteSpace()
可以方便地確定當前事件的性質。最后,方法 require(int
, String
, String
) 可以聲明預期的解析器狀態(tài);除非當前事件是指定的類型,并且本地名和名稱空間(如果給出的話)與當前事件匹配,否則該方法將拋出異常。
清單 4. 如果當前事件是
START_ELEMENT
使用有關的屬性方法
if (reader.getEventType() == XMLStreamConstants.START_ELEMENT) { System.out.println("Start Element: " + reader.getName()); for(int i = 0, n = reader.getAttributeCount(); i < n; ++i) { QName name = reader.getAttributeName(i); String value = reader.getAttributeValue(i); System.out.println("Attribute: " + name + "=" + value); } } |
創(chuàng)建之后,XMLStreamReader
將從 START_DOCUMENT
狀態(tài)開始(即 getEventType()
返回 START_DOCUMENT
)。處理標記的時候應考慮到這一點。和迭代器不同,不需要先移動指針(使用 next()
)來進入合法的狀態(tài)。同樣地,當讀取器轉換到最終狀態(tài) END_DOCUMENT
之后,應用程序也不應再移動它。在這種狀態(tài)下,hasNext()
方法將返回 false。
START_DOCUMENT
事件提供了獲取關于文檔本身信息的方法,如 getEncoding()
、getVersion()
和 isStandalone()
。應用程序也可調用 getProperty(String)
獲得命名屬性的值,不過一些屬性僅在特定狀態(tài)做了定義(比方說,如果當前事件是 DTD,則屬性 javax.xml.stream.notations
和 javax.xml.stream.entities
分別返回所有的符號和實體聲明)。
在 START_ELEMENT
和 END_ELEMENT
事件中,可以使用和元素名稱以及名稱空間有關的方法(如 getName()
、getLocalName()
、getPrefix()
和 getNamespaceXXX()
),在 START_ELEMENT
事件中還可使用與屬性有關的方法(getAttributeXXX()
)。
ATTRIBUTE
和 NAMESPACE
也被識別為獨立的事件,雖然在解析 典型的 XML 文檔時不會用到。但是當 ATTRIBUTE
或 NAMESPACE
節(jié)點作為 XPath 查詢結果返回時可以使用。
和基于文本的事件(如 CHARACTERS
、CDATA
、COMMENT
和 SPACE
),可使用各種 getTextXXX()
方法取得文本。可以分別使用 getPITarget()
和 getPIData()
檢索 PROCESSING_INSTRUCTION
的目標和數據。ENTITY_REFERENCE
和 DTD
也支持 getText()
,ENTITY_REFERENCE
還支持 getLocalName()
。
解析完成后,應用程序關閉讀取器并釋放解析過程中獲得的資源。請注意這樣并沒有關閉底層的輸入源。
清單 5 提供了一個完整的例子,使用基于指針的 API 處理 XML 文檔。首先取得 XMLInputFactory
的默認實例并創(chuàng)建一個 XMLStreamReader
解析給定的輸入流。然后不斷檢查讀取器的狀態(tài),根據當前事件的類型報告某些信息(比如在 START_ELEMENT
狀態(tài)下報告元素名及元素屬性)。最后,遇到 END_DOCUMENT
時關閉讀取器。
清單 5. 使用
XMLStreamReader
解析 XML 文檔的完整例子
XMLInputFactory factory = XMLInputFactory.newInstance(); XMLStreamReader r = factory.createXMLStreamReader(input); try { int event = r.getEventType(); while (true) { switch (event) { case XMLStreamConstants.START_DOCUMENT: out.println("Start Document."); break; case XMLStreamConstants.START_ELEMENT: out.println("Start Element: " + r.getName()); for(int i = 0, n = r.getAttributeCount(); i < n; ++i) out.println("Attribute: " + r.getAttributeName(i) + "=" + r.getAttributeValue(i)); break; case XMLStreamConstants.CHARACTERS: if (r.isWhiteSpace()) break; out.println("Text: " + r.getText()); break; case XMLStreamConstants.END_ELEMENT: out.println("End Element:" + r.getName()); break; case XMLStreamConstants.END_DOCUMENT: out.println("End Document."); break; } if (!r.hasNext()) break; event = r.next(); } } finally { r.close(); } |
通過調用 XMLInputFactory
的帶有基本讀取器的 createFilteredReader
方法和一個應用程序定義的篩選器(即實現 StreamFilter
的類實例),可以創(chuàng)建篩選過的 XMLStreamReader
。導航篩選過的讀取器時,讀取器每次移動到下一個標記之前都會詢問篩選器。如果篩選器認可了當前事件,就將其公開給篩選過的讀取器。否則跳過這個標記并檢查下一個,依此類推。這種方法可以讓開發(fā)人員創(chuàng)建一個僅處理解析內容子集的基于指針的 XML 處理程序,并與針對不同的擴展的內容模型的篩選器結合使用。
執(zhí)行更復雜的流操作,可以創(chuàng)建 StreamReaderDelegate
的子類并重寫合適的方法。然后使用這個子類的實例包裝基本 XMLStreamReader
,從而為應用程序提供一個修改過的基本 XML 流的視圖。可通過這種技術對 XML 流執(zhí)行簡單的轉換,比如篩掉或者替換特定的標記,甚至增加新的標記。
清單 6 用定制的 StreamReaderDelegate
包裝了基本 XMLStreamReader
,重寫了 next()
方法來跳過 COMMENT
和 PROCESSING_INSTRUCTION
事件。使用該讀取器時,應用程序不用擔心會遇到這種類型的標記。
清單 6. 使用定制的
StreamReaderDelegate
篩選注釋和處理指令
URL url = new URL(uri); InputStream input = url.openStream(); XMLInputFactory f = XMLInputFactory.newInstance(); XMLStreamReader r = f.createXMLStreamReader(uri, input); XMLStreamReader fr = new StreamReaderDelegate(r) { public int next() throws XMLStreamException { while (true) { int event = super.next(); switch (event) { case XMLStreamConstants.COMMENT: case XMLStreamConstants.PROCESSING_INSTRUCTION: continue; default: return event; } } } }; try { int event = fr.getEventType(); while (true) { switch (event) { case XMLStreamConstants.COMMENT: case XMLStreamConstants.PROCESSING_INSTRUCTION: // this should never happen throw new IllegalStateException("Filter failed!"); default: // process XML normally } if (!fr.hasNext()) break; event = fr.next(); } } finally { fr.close(); } input.close(); |
![]() ![]() |
![]()
|
可以看到,基于指針的 API 主要是為了提高效率。所有的狀態(tài)信息可以直接從流讀取器獲得,不需要創(chuàng)建額外的對象。非常適用于性能和低內存占用至關重要的應用程序。
人們早就認識到了拉式 XML 解析的好處。事實上,StAX 本身源于一種稱為 XML Pull Parsing 的方法。XML Pull Parser API 類似于 StAX 所提供的基于指針的 API,可以通過分析解析器的狀態(tài)獲得上一個解析事件的信息,然后移動到下一個,依此類推。但沒有提供基于事件迭代器的 API。這是一種非常輕型的方法,特別適合資源受限的環(huán)境,比如 J2ME。但是,很少有實現提供企業(yè)級特性如驗證,因此 XML Pull 一直未受到企業(yè) Java 開發(fā)人員的關注。
基于以往拉式解析器實現的經驗,StAX 的創(chuàng)建者選擇了在基于指針的 API 之外增加一種面向對象的 API。雖然 XMLEventReader
接口看起來似乎很簡單,但是基于事件迭代器的方法具有一個基于指針的方法不具備的重要優(yōu)點。通過將解析器事件變成一級對象,從而讓應用程序可以采用面向對象的方式處理它們。這樣做有助于模塊化和不同應用程序組件之間的代碼重用。
清單 7. 使用 StAX
XMLEventReader
解析 XML
XMLInputFactory inputFactory = XMLInputFactory.newInstance(); XMLEventReader reader = inputFactory.createXMLEventReader(input); try { while (reader.hasNext()) { XMLEvent e = reader.nextEvent(); if (e.isCharacters() && ((Characters) e).isWhiteSpace()) continue; out.println(e); } } finally { reader.close(); } |
![]() ![]() |
![]()
|
本文介紹了 StAX 及其基于指針的 API。第 2 部分將深入討論事件迭代器 API。
學習
- 您可以參閱本文在 developerWorks 全球網站上的 英文原文。
- 使用 StAX 解析 XML,第 2 部分: 拉式解析和事件(Peter Nehrer,developerWorks,2007 年 7 月):本系列第 2 部分介紹基于事件迭代器的 API 以及它為 Java 開發(fā)人員帶來的好處。
- 使用 StAX 解析 XML,第 3 部分: 使用定制事件和編寫 XML(Peter Nehrer,developerWorks,2007 年 7 月):將介紹定制化技術,該技術使用由應用程序定義的事件,您還將看到如何創(chuàng)建定制事件類并使用它們結合基于事件迭代器的 API 來處理 XML。最后(同樣也是重要的一點),將回顧由 StAX 提供的可將 XML 編寫為標記流和事件對象的序列化器 API。
- JSR 173: Streaming API for XML:閱讀這份 Java Specification Request,它提出了可拉式解析 XML 的基于 Java 的 API。
- XML Pull Parsing:這個站點致力于推廣和培訓拉式 XML 解析。
- BEA Dev2Dev Online: StAX:BEA 關于 StAX 的網頁,包括 WebLogic StAX 實現的鏈接。
- 利用 Java 技術進行 XML 編程,第 1 部分(Doug Tidwell,developerWorks,2004 年 2 月):獲得關于使用 Java 進行 XML 編程的教程,內容包括 XML 的通用 API,以及如何解析、創(chuàng)建、操作和轉換 XML 文檔。
- JAXP 全面介紹,第 1 部分(Brett McLaughlin,developerWorks,2005 年 6 月):了解如何使用 JAXP API 的解析和驗證特性。
- 技巧: 使用 XML 流解析器(Berthold Daum,developerWorks,2003 年 11 月):這篇技巧介紹了如何利用 StAX 提供的流解析器高效解析 XML。
- 技巧: 使用 StAX 編寫 XML 文檔(Berthold Daum,developerWorks,2004 年 1 月):使用 StAX 序列化器 XML —— 解析 XML 文檔并將 XML 文檔寫入一個輸出流。
- An XSLT style sheet and an XML dictionary approach to internationalization(Laura Menke,developerWorks,2001 年 4 月):閱讀 XSLT 用于解決實際問題的例子:通過詞典驅動的方法對網頁實現動態(tài)國際化,更新站點內容時減少需要編輯的文件。
- XSLT 處理程序是如何工作的(Benoît Marchal,developerWorks,2004 年 4 月):了解 XSLT 處理程序的有關理論,提高 XSLT 編程效率。
- 準備從 XSLT 1.0 升級到 2.0,第 1 部分: XSLT 的改進(David Marston 和 Joanne Tong,developerWorks,2006 年 11 月):對 XSLT 2.0 的主要特性和 XSLT 1.0 (目前使用的版本)的不足之處進行了介紹。
- IBM XML 認證:了解如何才能成為一名 IBM 認證的 XML 及相關技術的開發(fā)人員。
- XML 技術庫:developerWorks 中國網站 XML 專區(qū)提供了大量技術文章、技巧、教程、標準以及 IBM 紅皮書。
- developerWorks 技術活動和網絡廣播:隨時關注技術的最新進展。
- 通過 developerWorks 中國網站 XML 專區(qū) 了解 XML 的方方面面。
獲得產品和技術
- Sun 的 JAXP 項目頁:可以找到各種 JAXP 版本的下載鏈接。
討論
![]() |
||
|
![]() |
Peter Nehrer 是一名專長于基于 Eclipse 的企業(yè)解決方案和 Java EE 應用程序的軟件顧問。他創(chuàng)建了 Ecliptical Software Inc.,并且是一些和 Eclipse 有關的開放源碼項目的貢獻者。他擁有從馬薩諸塞州大學阿默斯特校區(qū)獲得的計算機科學碩士學位。 |