JAXP 驗證使用 JAXP 1.3 的新功能驗證 XML ![]() | ![]() |
![]() |
級別: 中級 Brett McLaughlin (brett@newInstance.com), 作家/編輯, O‘Reilly Media, Inc. 2005 年 11 月 03 日 |
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)。
![]() |
|
詳細了解這種驗證 API 的具體細節(jié)之前,必須充分了解 JAXP 1.3 之前驗證是如何完成的。此外,顯然 Sun 仍將支持過去的 DTD 驗證方法,但是建議使用基于模式的新的驗證 API。因此即便您義無反顧地要使用 javax.xml.validation
包,仍然需要理解使用 DTD 驗證文檔的方法。
在一般的 JAXP 處理中,都是從 工廠 開始的。SAXParserFactory
用于 SAX 解析,DocumentBuilderFactory
則用于 DOM 解析。這兩種工廠都使用靜態(tài)方法 newInstance()
創(chuàng)建,如清單 1 所示。
// Create a new SAX Parser factorySAXParserFactory factory = SAXParserFactory.newInstance();// Create a new DOM Document Builder factoryDocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
![]() |
|
雖然 SAXParserFactory
和 DocumentBuilderFactory
有分別適合 SAX 和 DOM 的不同特性和性質(zhì),但是對驗證來說它們都有一個共同的方法:setValidating()
。如您所料,要打開驗證,只需要把 true
傳遞給該方法。但是使用工廠是為了創(chuàng)建解析器而不是直接解析文檔。創(chuàng)建工廠之后就可以調(diào)用 newSAXParser()
(SAX)或 newDocumentBuilder()
(DOM)。清單 2 顯示了這兩個方法,都打開了驗證。
// 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 的解析完全沒有作用。
![]() ![]() |
![]()
|
5年前,用一個漂亮的方法打開 DTD 驗證就足夠了。甚至兩年前,XML Schema 和 RELAX NG之類的模式語言仍然在忙于解決自己的問題。但今天,用模式驗證文檔與 DTD 方式一樣常見。這兩種方法同時存在很大程度上是因為遺留文檔仍然使用DTD。今后幾年內(nèi),DTD 將和 Lisp 一樣消失,成為歷史遺跡而不是主流技術。
JAXP 1.3 通過引入 javax.xml.validation
包支持模式驗證已經(jīng)在開發(fā)人員中引起了很大反響。這個包易于使用,結構緊湊,已經(jīng)成為 Java 語言的標準組成部分。更好的是,如果您曾經(jīng)通過JAXP 使用過 SAX 和 DOM,那么掌握如何驗證就更簡單了。模型是類似的,您會發(fā)現(xiàn)使用這種 API 進行驗證簡直輕而易舉。
通過 簡要的歷史回顧 您知道,使用 SAX 的第一步是創(chuàng)建新的 SAXParserFactory
。如果使用 DOM 則首先創(chuàng)建 DocumentBuilderFactory
。因此毫不奇怪,進行模式驗證首先要創(chuàng)建 SchemaFactory
類的實例,如清單 3 所示。
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)在只需要知道兩個:
XMLConstants.RELAXNG_NS_URI
XMLConstants.W3C_XML_SCHEMA_NS_URI
因為 SchemaFactory
是與具體的約束模型聯(lián)系在一起的,所以必須在工廠構造的時候提供這個值。
SchemaFactory
類還有其他幾個選項。這些內(nèi)容在后面的 深入了解驗證 一節(jié)中再介紹。對于一般的 XML 驗證,預設的工廠就夠了。
![]() |
|
建立工廠后還需要裝入需要使用的約束集??梢酝ㄟ^工廠的 newSchema()
方法來完成。但是工廠以 javax.xml.transform.Source
實現(xiàn)作為輸入,因此需要一個中間步驟:將模式轉化成 Source
表示。這個過程很簡單,如清單 4 所示。
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。
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 的應用程序而言,文件驗證失敗可能根本不是異常,僅僅可能的結果之一。比方說支持 XML 的編輯器或者 IDE。在這些環(huán)境中,無效的 XML 不應該造成系統(tǒng)崩潰和關閉。另外,如果只能以 Exception
形式報告錯誤 ,就過于沉重了。
當然,對于 JAXP 老手這并不新鮮,您可能已經(jīng)習慣為 SAXParser
或 DocumentBuilder
提供 org.xml.sax.ErrorHandler
。這個接口提供的三個方法 warning()
、error()
和 fatalError()
簡化了解析中的錯誤處理。幸運的是,驗證 XML 時也有相同的設施可用。更好的是,使用的還是同一個接口。正是如此,ErrorHandler
接口在驗證中與在解析中一樣有用。清單 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ù)驗證。如果熟悉這個接口,完全不需要再重新學習!
![]() |
|
某些很少見的情況下,可能需要從多個模式構造 Schema
對象。這有點兒費解;一個 Schema
不是 對應一個模式或文件。相反,該對象表示一組約束。這些約束可以來一個文件,也可以來自多個文件。因此,可以通過 newSchema(Source[] sourceList)
為 newSchema()
方法提供一個 Source
實現(xiàn)數(shù)組(表示多個約束)。返回的仍然是一個 Schema
對象,表示所提供的模式的組合。
可以預料,這種情況下會出現(xiàn)很多錯誤。因此建議為 SchemaFactory
設置 ErrorHandler
(更多信息參見 處理錯誤 一節(jié))。很多地方都可能出問題,因此要準備好在出現(xiàn)的時候解決問題。
到目前為止,我們一直把驗證作為獨立于解析的單獨部分。但是并非必須如此。得到 Schema
對象后,就可以將其賦給 SAXParserFactory
或 DocumentBuilderFactory
,都通過 setSchema()
方法(參見清單 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,而是說應該使用盡可能多的樣本文檔,并對驗證結果檢查兩次,對錯誤處理要小心謹慎。
![]() ![]() |
![]()
|
坦白地說,JAXP 驗證 API 并沒有明顯的新東西??梢岳^續(xù)使用 SAX 或 DOM 解析和驗證 XML,并結合 SAX 的 ErrorHandler
類,通過巧妙的編程也能對驗證錯誤進行即時處理。但是這需要對 SAX 有充分的了解,需要很多時間去測試和調(diào)試并且仔細地管理內(nèi)存(如果最終創(chuàng)建 DOM Document
對象的話)。這正是 JAXP 驗證 API閃光的地方。它提供了一種經(jīng)過認真測試的、可以隨時使用的解決方案,而不僅僅是是否啟用模式驗證的一個開關。它很容易與已有的 JAXP代碼結合在一起,增加模式驗證非常簡單。我相信,長期使用 XML 的 Java 開發(fā)人員一定會發(fā)現(xiàn) JAXP 驗證的一些優(yōu)點。
http://blog.csdn.net/haydenwang8287/archive/2007/09/13/1784398.aspx