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