http://www-128.ibm.com/developerworks/cn/xml/x-jaxpval.html
JAXP 驗證
使用 JAXP 1.3 的新功能驗證 XML
 |
 |
Java™
編程語言的最新版本 Java 5.0 包括經(jīng)過改進和擴展的 Java API for XML Processing(JAXP)版本。JAXP
主要增加了新的驗證 API,它提供了更好的交互性,支持 XML Schema 和 RELAX NG,能夠在驗證的同時即時修改。經(jīng)過這些改進,為
Java 開發(fā)人員提供了一種工業(yè)強度的 XML 驗證解決方案。本文詳細介紹這種新的 API,包括基本特性和更高級的特性。
幾
年來,Java API for XML Processing(JAXP)一直是一種穩(wěn)定、有點兒沉悶的
API。這并不是壞事。沉悶常常意味著可靠,對軟件來說總是好事。不過 JAXP 的遲鈍已經(jīng)讓開發(fā)人員不再尋求新的特性。從 Java 1.3 到
1.4,除了支持最新版本的 SAX 和 DOM 規(guī)范(請參閱 參考資料)以外,JAXP 沒有很大變化。但是在 Java 5.0 和 JAXP 1.3 中,Sun 大大擴展了 JAXP。除了支持 XPath 以外,最值得一提的還有驗證。本文詳細介紹了 JAXP 1.3 的驗證特性,該特性在 javax.xml.validation 包中實現(xiàn)。
簡要的歷史回顧
 |
無所不在的模式
本文中(而且一般來說),模式(schema) 指的是跟隨一種 XML 格式的任何約束模型。XML Schema 是一種模式,但模式不一定是 XML Schema(按照 W3C 規(guī)范的定義)。比如,模式 也可用于 RELAX NG 模式。使用一般意義的 模式 更便于指稱某種特定的方法(基于 XML 的約束模型)而不局限于具體的實現(xiàn)。
|
|
詳細了解這種驗證 API 的具體細節(jié)之前,必須充分了解 JAXP 1.3 之前驗證是如何完成的。此外,顯然 Sun 仍將支持過去的 DTD 驗證方法,但是建議使用基于模式的新的驗證 API。因此即便您義無反顧地要使用 javax.xml.validation 包,仍然需要理解使用 DTD 驗證文檔的方法。
創(chuàng)建解析器工廠
在一般的 JAXP 處理中,都是從 工廠 開始的。SAXParserFactory 用于 SAX 解析,DocumentBuilderFactory 則用于 DOM 解析。這兩種工廠都使用靜態(tài)方法 newInstance() 創(chuàng)建,如清單 1 所示。
清單 1. 創(chuàng)建 SAXParserFactory 和 DocumentBuilderFactory
// Create a new SAX Parser factorySAXParserFactory factory = SAXParserFactory.newInstance();// Create a new DOM Document Builder factoryDocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
打開驗證
 |
一個工廠,多個解析器
對工廠設置的選項影響該工廠創(chuàng)建的所有解析器。如果用 true 調(diào)用 setValidating(),則明確地告訴工廠創(chuàng)建的所有解析器都必須是進行驗證的。要記住,很容易出現(xiàn)這種情況:在工廠中打開驗證,在寫了 100 行代碼之后忘了這個設置,也就忘了生成的解析器是進行驗證的。
|
|
雖然 SAXParserFactory 和 DocumentBuilderFactory 有分別適合 SAX 和 DOM 的不同特性和性質(zhì),但是對驗證來說它們都有一個共同的方法:setValidating()。如您所料,要打開驗證,只需要把 true 傳遞給該方法。但是使用工廠是為了創(chuàng)建解析器而不是直接解析文檔。創(chuàng)建工廠之后就可以調(diào)用 newSAXParser()(SAX)或 newDocumentBuilder()(DOM)。清單 2 顯示了這兩個方法,都打開了驗證。
清單 2. 打開驗證(DTD)
// Create a new SAX Parser factorySAXParserFactory factory = SAXParserFactory.newInstance();// Turn on validationfactory.setValidating(true);// Create a validating SAX parser instanceSAXParser parser = factory.newSAXParser();// Create a new DOM Document Builder factoryDocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();// Turn on validationfactory.setValidating(true);// Create a validating DOM parserDocumentBuilder builder = factory.newDocumentBuilder();
|
無論哪種情況,都將得到一個能夠解析 XML 并在解析過程中驗證 XML 的對象(SAXParser 或 DocumentBuilder)。但是要記住,這樣做 僅 限于 DTD 解析。setValidating(true) 調(diào)用對基于 XML 的解析完全沒有作用。
javax.xml.validation 簡介
5
年前,用一個漂亮的方法打開 DTD 驗證就足夠了。甚至兩年前,XML Schema 和 RELAX NG
之類的模式語言仍然在忙于解決自己的問題。但今天,用模式驗證文檔與 DTD 方式一樣常見。這兩種方法同時存在很大程度上是因為遺留文檔仍然使用
DTD。今后幾年內(nèi),DTD 將和 Lisp 一樣消失,成為歷史遺跡而不是主流技術。
JAXP 1.3 通過引入 javax.xml.validation
包支持模式驗證已經(jīng)在開發(fā)人員中引起了很大反響。這個包易于使用,結(jié)構(gòu)緊湊,已經(jīng)成為 Java 語言的標準組成部分。更好的是,如果您曾經(jīng)通過
JAXP 使用過 SAX 和 DOM,那么掌握如何驗證就更簡單了。模型是類似的,您會發(fā)現(xiàn)使用這種 API 進行驗證簡直輕而易舉。
使用 SchemaFactory
通過 簡要的歷史回顧 您知道,使用 SAX 的第一步是創(chuàng)建新的 SAXParserFactory。如果使用 DOM 則首先創(chuàng)建 DocumentBuilderFactory。因此毫不奇怪,進行模式驗證首先要創(chuàng)建 SchemaFactory 類的實例,如清單 3 所示。
清單 3. 創(chuàng)建 SchemaFactory
import javax.xml.XMLConstants;import javax.xml.validation.SchemaFactory;...SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_SCHEMA_NS_URI);
|
這和其他工廠的創(chuàng)建類似,只不過增加了傳遞給 newInstance() 方法的參數(shù)。必須向該方法傳遞另一個類中定義的常量,即 javax.xml.XMLConstants 類,對這個類也需要非常熟悉。這個類定義了 JAXP 應用程序中使用的所有常量,不過現(xiàn)在只需要知道兩個:
- 用于 RELAX NG 模式的
XMLConstants.RELAXNG_NS_URI
- 用于 W3C XML Schema 的
XMLConstants.W3C_XML_SCHEMA_NS_URI
因為 SchemaFactory 是與具體的約束模型聯(lián)系在一起的,所以必須在工廠構(gòu)造的時候提供這個值。
SchemaFactory 類還有其他幾個選項。這些內(nèi)容在后面的 深入了解驗證 一節(jié)中再介紹。對于一般的 XML 驗證,預設的工廠就夠了。
針對模式進行驗證
 |
Use the Source, Luke
盡管這個標題威嚴、一語雙關,其實在整個 JAXP 中 Source 接口非常重要。該接口源自 XML 轉(zhuǎn)換處理,已經(jīng)成為各種 JAXP 結(jié)構(gòu)的輸入標準,至少對于沒有直接使用 Java 語言 IO 類的情況是這樣。如果從未使用過 Source 及其實現(xiàn),請看一下 參考資料 中關于 XML 轉(zhuǎn)換的文檔和文章。
|
|
建立工廠后還需要裝入需要使用的約束集??梢酝ㄟ^工廠的 newSchema() 方法來完成。但是工廠以 javax.xml.transform.Source 實現(xiàn)作為輸入,因此需要一個中間步驟:將模式轉(zhuǎn)化成 Source 表示。這個過程很簡單,如清單 4 所示。
清單 4. 從約束到 Schema
import javax.xml.XMLConstants;import javax.xml.transform.Source;import javax.xml.transform.stream.StreamSource;import javax.xml.validation.SchemaFactory;import javax.xml.validation.Schema;...SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_SCHEMA_NS_URI);Source schemaSource = new StreamSource(new File("constraints.xml"));Schema schema = schemaFactory.newSchema(schemaSource);
|
如果熟悉 JAXP,那么這些代碼都非常直觀。清單 4 中加載了一個名為 constraints.xml 的文件??梢允褂萌魏畏椒ǖ玫?Source 中的數(shù)據(jù),包括使用 SAX 或 DOM(分別通過 SAXSource 和 DOMSource)讀取約束,甚至使用 URL。
一旦得到了 Source 實現(xiàn),就將其傳遞給工廠的 newSchema() 方法。返回的就是 Schema。現(xiàn)在,對文檔進行驗證就很簡單了。請參閱清單 5。
清單 5. 驗證 XML
import javax.xml.XMLConstants;import javax.xml.transform.Source;import javax.xml.transform.stream.StreamSource;import javax.xml.validation.SchemaFactory;import javax.xml.validation.Schema;import javax.xml.validation.Validator;...SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_SCHEMA_NS_URI);Source schemaSource = new StreamSource(new File("constraints.xml"));Schema schema = schemaFactory.newSchema(schemaSource);Validator validator = schema.newValidator();validator.validate(new StreamSource("my-file.xml"));
|
這里同樣沒有什么大的變化。只要知道要使用的類和調(diào)用的方法就很容易了。因為要進行驗證,所以必須使用 Validator 類。可以使用 newValidator() 方法從 Schema 得到這個類的實例。最后可以調(diào)用 validate() 并再次傳遞 Source 實現(xiàn),不過這一次它代表要解析和驗證的 XML。
調(diào)用該方法之后就會解析和驗證目標 XML。要記住,即使用 DOMSource 提供 XML(解析過的 XML 表示),解析也可能再次發(fā)生。驗證仍然和解析緊密聯(lián)系在一起,因此驗證過程需要一點兒時間。
如果出現(xiàn)錯誤,就會拋出異常說明出了問題。JAXP 的多數(shù)實現(xiàn)都包括行號,有時候還有列號,幫助定位違反約束模型的位置。當然,僅僅拋出異常并不一定是解決問題的最佳方式。我將在 下一節(jié) 介紹一種更好的方法。
看起來似乎工作不少:得到工廠,得到模式,得到驗證器。讓 JAXP 提供一個工廠方法來完成這一切是完全可能的,比方說 validate(Source schema, Source xmlDocument) 這樣的方法。但是模塊化有一定的好處,在 下一節(jié) 中將看到同時使用 Schema 和 Validator 類,可以解決 XML 處理中某些非常奇特的個別情況。而且如果確實需要可以自己編寫,不妨當作一個很好的練習!
深入了解驗證
對于很多應用程序來說,上面介紹的這些內(nèi)容就足夠了。您可以把輸入文檔和模式交給一個方法讓它去驗證。簡單的 Exception 告訴您遇到了問題,甚至還提供了一些解決問題的基本信息。對于將 XML 作為數(shù)據(jù)格式的應用程序,可能僅僅是傳遞某些信息,關于 JAXP 的驗證功能可能知道這些就足夠了。
但是,我們生活在一個到處都是 XML 編輯器、文件和代碼生成器以及 Web 服務的世界中。對于這類應用程序,XML 就不僅僅起輔助作用,而 是 應用程序本身,基本的驗證常常就不夠了。對于這類應用程序,JAXP 提供了很多特性,這是下面要討論的。
處理錯誤
首先,人們認為 Exception 表明發(fā)生了異常的行為。但是對于基于 XML 的應用程序而言,文件驗證失敗可能根本不是異常,僅僅可能的結(jié)果之一。比方說支持 XML 的編輯器或者 IDE。在這些環(huán)境中,無效的 XML 不應該造成系統(tǒng)崩潰和關閉。另外,如果只能以 Exception 形式報告錯誤 ,就過于沉重了。
當然,對于 JAXP 老手這并不新鮮,您可能已經(jīng)習慣為 SAXParser 或 DocumentBuilder 提供 org.xml.sax.ErrorHandler。這個接口提供的三個方法 warning()、error() 和 fatalError() 簡化了解析中的錯誤處理。幸運的是,驗證 XML 時也有相同的設施可用。更好的是,使用的還是同一個接口。正是如此,ErrorHandler 接口在驗證中與在解析中一樣有用。清單 6 提供了一個簡單的例子。
清單 6. 處理驗證錯誤
import javax.xml.XMLConstants;import javax.xml.transform.Source;import javax.xml.transform.stream.StreamSource;import javax.xml.validation.SchemaFactory;import javax.xml.validation.Schema;import javax.xml.validation.Validator;import org.xml.sax.ErrorHandler;...SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_SCHEMA_NS_URI);Source schemaSource = new StreamSource(new File("constraints.xml"));Schema schema = schemaFactory.newSchema(schemaSource);Validator validator = schema.newValidator();ErrorHandler mySchemaErrorHandler = new MySchemaErrorHandler();validator.setErrorHandler(mySchemaErrorHandler);validator.validate(new StreamSource("my-file.xml"));
|
和 SAX 一樣,可以使用該接口自定義錯誤的處理。從而讓應用程序從容地退出驗證、打印錯誤消息,甚至可以嘗試從錯誤中恢復并繼續(xù)驗證。如果熟悉這個接口,完全不需要再重新學習!
裝入多個模式
 |
一個又一個 setErrorHandler()
如果閱讀 javax.xml.validation 包的 JavaDoc,可能會注意到 SchemaFactory 以及 Schema 類上的 setErrorHandler() 方法。如果為 SchemaFactory 設置異常處理程序,就可以處理在 newSchema() 調(diào)用過程中解析模式時出現(xiàn)的錯誤。因此這也是驗證 API 的一部分,不過不適用于模式驗證錯誤而是用于模式解析錯誤。
|
|
某些很少見的情況下,可能需要從多個模式構(gòu)造 Schema 對象。這有點兒費解;一個 Schema 不是 對應一個模式或文件。相反,該對象表示一組約束。這些約束可以來一個文件,也可以來自多個文件。因此,可以通過 newSchema(Source[] sourceList) 為 newSchema() 方法提供一個 Source 實現(xiàn)數(shù)組(表示多個約束)。返回的仍然是一個 Schema 對象,表示所提供的模式的組合。
可以預料,這種情況下會出現(xiàn)很多錯誤。因此建議為 SchemaFactory 設置 ErrorHandler(更多信息參見 處理錯誤 一節(jié))。很多地方都可能出問題,因此要準備好在出現(xiàn)的時候解決問題。
把驗證集成到解析中
到目前為止,我們一直把驗證作為獨立于解析的單獨部分。但是并非必須如此。得到 Schema 對象后,就可以將其賦給 SAXParserFactory 或 DocumentBuilderFactory,都通過 setSchema() 方法(參見清單 7)。
清單 7. 把驗證集成到解析中
// Load up the documentDocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();// Set up an XML Schema validator, using the supplied schemaSource schemaSource = new StreamSource(new File(args[1]));SchemaFactory schemaFactory = SchemaFactory.newInstance( XMLConstants.W3C_XML_SCHEMA_NS_URI);Schema schema = schemaFactory.newSchema(schemaSource);// Instead of explicitly validating, assign the Schema to the factoryfactory.setSchema(schema);// Parsers from this factory will automatically validate against the// associated schemaDocumentBuilder builder = factory.newDocumentBuilder();Document doc = builder.parse(new File(args[0]));
|
要注意,這里 不 需要使用 setValidating() 方法顯式地打開驗證。任何 Schema 不是 null 的工廠所創(chuàng)建的解析器都會使用那個 Schema 進行驗證??梢灶A料,驗證錯誤都會報告給解析器設置的 ErrorHandler。
重要的警告
雖
然看起來不錯,我認為還不夠好,JAXP 的新驗證 API 存在一些嚴重的問題。首先,即使在 Java 5.0 和 JAXP 1.3
正式版中,我也發(fā)現(xiàn)有很多錯誤和奇怪的行為。新的 API
仍然在增加解析器支持,這意味著個別情況(很少使用的特性)僅僅部分實現(xiàn)了(有時候根本沒有實現(xiàn))。我發(fā)現(xiàn)很多時候,能夠通過獨立驗證器如
xmllint(請參閱 參考資料)驗證的文檔卻不能通過 JAXP 的驗證。
直接使用 Validator 類和 validate() 方法,與將 Schema 賦給 SAXParserFactory 或 DocumentBuilderFactory 相比,似乎更可靠。建議您采用比較保險的辦法。我并不是要求您避開這種 API,而是說應該使用盡可能多的樣本文檔,并對驗證結(jié)果檢查兩次,對錯誤處理要小心謹慎。
結(jié)束語
坦白地說,JAXP 驗證 API 并沒有明顯的新東西。可以繼續(xù)使用 SAX 或 DOM 解析和驗證 XML,并結(jié)合 SAX 的 ErrorHandler 類,通過巧妙的編程也能對驗證錯誤進行即時處理。但是這需要對 SAX 有充分的了解,需要很多時間去測試和調(diào)試并且仔細地管理內(nèi)存(如果最終創(chuàng)建 DOM Document
對象的話)。這正是 JAXP 驗證 API
閃光的地方。它提供了一種經(jīng)過認真測試的、可以隨時使用的解決方案,而不僅僅是是否啟用模式驗證的一個開關。它很容易與已有的 JAXP
代碼結(jié)合在一起,增加模式驗證非常簡單。我相信,長期使用 XML 的 Java 開發(fā)人員一定會發(fā)現(xiàn) JAXP 驗證的一些優(yōu)點。
http://blog.csdn.net/haydenwang8287/archive/2007/09/13/1784398.aspx
|