http://www-128.ibm.com/developerworks/cn/xml/x-jaxpval.html
JAXP 验证
使用 JAXP 1.3 的新功能验证 XML
 |
 |
Java™
编程语言的最新版本 Java 5.0 包括经过改进和扩展的 Java API for XML Processing(JAXP)版本。JAXP
主要增加了新的验证 API,它提供了更好的交互性,支持 XML Schema 和 RELAX NG,能够在验证的同时即时修改。经过这些改进,为
Java 开发人员提供了一种工业强度的 XML 验证解决方案。本文详细介绍这种新的 API,包括基本特性和更高级的特性。
几
年来,Java API for XML Processing(JAXP)一直是一种稳定、有点儿沉闷的
API。这并不是坏事。沉闷常常意味着可靠,对软件来说总是好事。不过 JAXP 的迟钝已经让开发人员不再寻求新的特性。从 Java 1.3 到
1.4,除了支持最新版本的 SAX 和 DOM 规范(请参阅 参考资料)以外,JAXP 没有很大变化。但是在 Java 5.0 和 JAXP 1.3 中,Sun 大大扩展了 JAXP。除了支持 XPath 以外,最值得一提的还有验证。本文详细介绍了 JAXP 1.3 的验证特性,该特性在 javax.xml.validation 包中实现。
简要的历史回顾
 |
无所不在的模式
本文中(而且一般来说),模式(schema) 指的是跟随一种 XML 格式的任何约束模型。XML Schema 是一种模式,但模式不一定是 XML Schema(按照 W3C 规范的定义)。比如,模式 也可用于 RELAX NG 模式。使用一般意义的 模式 更便于指称某种特定的方法(基于 XML 的约束模型)而不局限于具体的实现。
|
|
详细了解这种验证 API 的具体细节之前,必须充分了解 JAXP 1.3 之前验证是如何完成的。此外,显然 Sun 仍将支持过去的 DTD 验证方法,但是建议使用基于模式的新的验证 API。因此即便您义无反顾地要使用 javax.xml.validation 包,仍然需要理解使用 DTD 验证文档的方法。
创建解析器工厂
在一般的 JAXP 处理中,都是从 工厂 开始的。SAXParserFactory 用于 SAX 解析,DocumentBuilderFactory 则用于 DOM 解析。这两种工厂都使用静态方法 newInstance() 创建,如清单 1 所示。
清单 1. 创建 SAXParserFactory 和 DocumentBuilderFactory
// Create a new SAX Parser factorySAXParserFactory factory = SAXParserFactory.newInstance();// Create a new DOM Document Builder factoryDocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
|
打开验证
 |
一个工厂,多个解析器
对工厂设置的选项影响该工厂创建的所有解析器。如果用 true 调用 setValidating() ,则明确地告诉工厂创建的所有解析器都必须是进行验证的。要记住,很容易出现这种情况:在工厂中打开验证,在写了 100 行代码之后忘了这个设置,也就忘了生成的解析器是进行验证的。
|
|
虽然 SAXParserFactory 和 DocumentBuilderFactory 有分别适合 SAX 和 DOM 的不同特性和性质,但是对验证来说它们都有一个共同的方法:setValidating() 。如您所料,要打开验证,只需要把 true 传递给该方法。但是使用工厂是为了创建解析器而不是直接解析文档。创建工厂之后就可以调用 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) 调用对基于 XML 的解析完全没有作用。
javax.xml.validation 简介
5
年前,用一个漂亮的方法打开 DTD 验证就足够了。甚至两年前,XML Schema 和 RELAX NG
之类的模式语言仍然在忙于解决自己的问题。但今天,用模式验证文档与 DTD 方式一样常见。这两种方法同时存在很大程度上是因为遗留文档仍然使用
DTD。今后几年内,DTD 将和 Lisp 一样消失,成为历史遗迹而不是主流技术。
JAXP 1.3 通过引入 javax.xml.validation
包支持模式验证已经在开发人员中引起了很大反响。这个包易于使用,结构紧凑,已经成为 Java 语言的标准组成部分。更好的是,如果您曾经通过
JAXP 使用过 SAX 和 DOM,那么掌握如何验证就更简单了。模型是类似的,您会发现使用这种 API 进行验证简直轻而易举。
使用 SchemaFactory
通过 简要的历史回顾 您知道,使用 SAX 的第一步是创建新的 SAXParserFactory 。如果使用 DOM 则首先创建 DocumentBuilderFactory 。因此毫不奇怪,进行模式验证首先要创建 SchemaFactory 类的实例,如清单 3 所示。
清单 3. 创建 SchemaFactory
import javax.xml.XMLConstants;import javax.xml.validation.SchemaFactory;...SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_SCHEMA_NS_URI);
|
这和其他工厂的创建类似,只不过增加了传递给 newInstance() 方法的参数。必须向该方法传递另一个类中定义的常量,即 javax.xml.XMLConstants 类,对这个类也需要非常熟悉。这个类定义了 JAXP 应用程序中使用的所有常量,不过现在只需要知道两个:
- 用于 RELAX NG 模式的
XMLConstants.RELAXNG_NS_URI
- 用于 W3C XML Schema 的
XMLConstants.W3C_XML_SCHEMA_NS_URI
因为 SchemaFactory 是与具体的约束模型联系在一起的,所以必须在工厂构造的时候提供这个值。
SchemaFactory 类还有其他几个选项。这些内容在后面的 深入了解验证 一节中再介绍。对于一般的 XML 验证,预设的工厂就够了。
针对模式进行验证
 |
Use the Source, Luke
尽管这个标题威严、一语双关,其实在整个 JAXP 中 Source 接口非常重要。该接口源自 XML 转换处理,已经成为各种 JAXP 结构的输入标准,至少对于没有直接使用 Java 语言 IO 类的情况是这样。如果从未使用过 Source 及其实现,请看一下 参考资料 中关于 XML 转换的文档和文章。
|
|
建立工厂后还需要装入需要使用的约束集。可以通过工厂的 newSchema() 方法来完成。但是工厂以 javax.xml.transform.Source 实现作为输入,因此需要一个中间步骤:将模式转化成 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 中的数据,包括使用 SAX 或 DOM(分别通过 SAXSource 和 DOMSource )读取约束,甚至使用 URL。
一旦得到了 Source 实现,就将其传递给工厂的 newSchema() 方法。返回的就是 Schema 。现在,对文档进行验证就很简单了。请参阅清单 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"));
|
这里同样没有什么大的变化。只要知道要使用的类和调用的方法就很容易了。因为要进行验证,所以必须使用 Validator 类。可以使用 newValidator() 方法从 Schema 得到这个类的实例。最后可以调用 validate() 并再次传递 Source 实现,不过这一次它代表要解析和验证的 XML。
调用该方法之后就会解析和验证目标 XML。要记住,即使用 DOMSource 提供 XML(解析过的 XML 表示),解析也可能再次发生。验证仍然和解析紧密联系在一起,因此验证过程需要一点儿时间。
如果出现错误,就会抛出异常说明出了问题。JAXP 的多数实现都包括行号,有时候还有列号,帮助定位违反约束模型的位置。当然,仅仅抛出异常并不一定是解决问题的最佳方式。我将在 下一节 介绍一种更好的方法。
看起来似乎工作不少:得到工厂,得到模式,得到验证器。让 JAXP 提供一个工厂方法来完成这一切是完全可能的,比方说 validate(Source schema, Source xmlDocument) 这样的方法。但是模块化有一定的好处,在 下一节 中将看到同时使用 Schema 和 Validator 类,可以解决 XML 处理中某些非常奇特的个别情况。而且如果确实需要可以自己编写,不妨当作一个很好的练习!
深入了解验证
对于很多应用程序来说,上面介绍的这些内容就足够了。您可以把输入文档和模式交给一个方法让它去验证。简单的 Exception 告诉您遇到了问题,甚至还提供了一些解决问题的基本信息。对于将 XML 作为数据格式的应用程序,可能仅仅是传递某些信息,关于 JAXP 的验证功能可能知道这些就足够了。
但是,我们生活在一个到处都是 XML 编辑器、文件和代码生成器以及 Web 服务的世界中。对于这类应用程序,XML 就不仅仅起辅助作用,而 是 应用程序本身,基本的验证常常就不够了。对于这类应用程序,JAXP 提供了很多特性,这是下面要讨论的。
处理错误
首先,人们认为 Exception 表明发生了异常的行为。但是对于基于 XML 的应用程序而言,文件验证失败可能根本不是异常,仅仅可能的结果之一。比方说支持 XML 的编辑器或者 IDE。在这些环境中,无效的 XML 不应该造成系统崩溃和关闭。另外,如果只能以 Exception 形式报告错误 ,就过于沉重了。
当然,对于 JAXP 老手这并不新鲜,您可能已经习惯为 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 一样,可以使用该接口自定义错误的处理。从而让应用程序从容地退出验证、打印错误消息,甚至可以尝试从错误中恢复并继续验证。如果熟悉这个接口,完全不需要再重新学习!
装入多个模式
 |
一个又一个 setErrorHandler()
如果阅读 javax.xml.validation 包的 JavaDoc,可能会注意到 SchemaFactory 以及 Schema 类上的 setErrorHandler() 方法。如果为 SchemaFactory 设置异常处理程序,就可以处理在 newSchema() 调用过程中解析模式时出现的错误。因此这也是验证 API 的一部分,不过不适用于模式验证错误而是用于模式解析错误。
|
|
某些很少见的情况下,可能需要从多个模式构造 Schema 对象。这有点儿费解;一个 Schema 不是 对应一个模式或文件。相反,该对象表示一组约束。这些约束可以来一个文件,也可以来自多个文件。因此,可以通过 newSchema(Source[] sourceList) 为 newSchema() 方法提供一个 Source 实现数组(表示多个约束)。返回的仍然是一个 Schema 对象,表示所提供的模式的组合。
可以预料,这种情况下会出现很多错误。因此建议为 SchemaFactory 设置 ErrorHandler (更多信息参见 处理错误 一节)。很多地方都可能出问题,因此要准备好在出现的时候解决问题。
把验证集成到解析中
到目前为止,我们一直把验证作为独立于解析的单独部分。但是并非必须如此。得到 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 的工厂所创建的解析器都会使用那个 Schema 进行验证。可以预料,验证错误都会报告给解析器设置的 ErrorHandler 。
重要的警告
虽
然看起来不错,我认为还不够好,JAXP 的新验证 API 存在一些严重的问题。首先,即使在 Java 5.0 和 JAXP 1.3
正式版中,我也发现有很多错误和奇怪的行为。新的 API
仍然在增加解析器支持,这意味着个别情况(很少使用的特性)仅仅部分实现了(有时候根本没有实现)。我发现很多时候,能够通过独立验证器如
xmllint(请参阅 参考资料)验证的文档却不能通过 JAXP 的验证。
直接使用 Validator 类和 validate() 方法,与将 Schema 赋给 SAXParserFactory 或 DocumentBuilderFactory 相比,似乎更可靠。建议您采用比较保险的办法。我并不是要求您避开这种 API,而是说应该使用尽可能多的样本文档,并对验证结果检查两次,对错误处理要小心谨慎。
结束语
坦白地说,JAXP 验证 API 并没有明显的新东西。可以继续使用 SAX 或 DOM 解析和验证 XML,并结合 SAX 的 ErrorHandler 类,通过巧妙的编程也能对验证错误进行即时处理。但是这需要对 SAX 有充分的了解,需要很多时间去测试和调试并且仔细地管理内存(如果最终创建 DOM Document
对象的话)。这正是 JAXP 验证 API
闪光的地方。它提供了一种经过认真测试的、可以随时使用的解决方案,而不仅仅是是否启用模式验证的一个开关。它很容易与已有的 JAXP
代码结合在一起,增加模式验证非常简单。我相信,长期使用 XML 的 Java 开发人员一定会发现 JAXP 验证的一些优点。
http://blog.csdn.net/haydenwang8287/archive/2007/09/13/1784398.aspx
|