JAXP 验证-使用 JAXP 1.3 的新功能验证 XML

http://www-128.ibm.com/developerworks/cn/xml/x-jaxpval.html

JAXP 验证

使用 JAXP 1.3 的新功能验证 XML


级别: 中级

Brett McLaughlin ([email protected]), 作家/编辑, O'Reilly Media, Inc.

2005 年 11 月 03 日

Java™ 编程语言的最新版本 Java 5.0 包括经过改进和扩展的 Java API for XML Processing(JAXP)版本。JAXP 主要增加了新的验证 API,它提供了更好的交互性,支持 XML Schema 和 RELAX NG,能够在验证的同时即时修改。经过这些改进,为 Java 开发人员提供了一种工业强度的 XML 验证解决方案。本文详细介绍这种新的 API,包括基本特性和更高级的特性。
<!--START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!--END RESERVED FOR FUTURE USE INCLUDE FILES-->

几年来,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 factory
SAXParserFactory factory = SAXParserFactory.newInstance();
// Create a new DOM Document Builder factory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

打开验证

一个工厂,多个解析器

对工厂设置的选项影响该工厂创建的所有解析器。如果用 true 调用 setValidating(),则明确地告诉工厂创建的所有解析器都必须是进行验证的。要记住,很容易出现这种情况:在工厂中打开验证,在写了 100 行代码之后忘了这个设置,也就忘了生成的解析器是进行验证的。

虽然 SAXParserFactoryDocumentBuilderFactory 有分别适合 SAX 和 DOM 的不同特性和性质,但是对验证来说它们都有一个共同的方法:setValidating()。如您所料,要打开验证,只需要把 true 传递给该方法。但是使用工厂是为了创建解析器而不是直接解析文档。创建工厂之后就可以调用 newSAXParser()(SAX)或 newDocumentBuilder()(DOM)。清单 2 显示了这两个方法,都打开了验证。


清单 2. 打开验证(DTD)
				// Create a new SAX Parser factory
SAXParserFactory factory = SAXParserFactory.newInstance();
// Turn on validation
factory.setValidating(true);
// Create a validating SAX parser instance
SAXParser parser = factory.newSAXParser();
// Create a new DOM Document Builder factory
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Turn on validation
factory.setValidating(true);
// Create a validating DOM parser
DocumentBuilder builder = factory.newDocumentBuilder(); 

无论哪种情况,都将得到一个能够解析 XML 并在解析过程中验证 XML 的对象(SAXParserDocumentBuilder)。但是要记住,这样做 限于 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(分别通过 SAXSourceDOMSource)读取约束,甚至使用 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) 这样的方法。但是模块化有一定的好处,在 下一节 中将看到同时使用 SchemaValidator 类,可以解决 XML 处理中某些非常奇特的个别情况。而且如果确实需要可以自己编写,不妨当作一个很好的练习!





回页首


深入了解验证

对于很多应用程序来说,上面介绍的这些内容就足够了。您可以把输入文档和模式交给一个方法让它去验证。简单的 Exception 告诉您遇到了问题,甚至还提供了一些解决问题的基本信息。对于将 XML 作为数据格式的应用程序,可能仅仅是传递某些信息,关于 JAXP 的验证功能可能知道这些就足够了。

但是,我们生活在一个到处都是 XML 编辑器、文件和代码生成器以及 Web 服务的世界中。对于这类应用程序,XML 就不仅仅起辅助作用,而 应用程序本身,基本的验证常常就不够了。对于这类应用程序,JAXP 提供了很多特性,这是下面要讨论的。

处理错误

首先,人们认为 Exception 表明发生了异常的行为。但是对于基于 XML 的应用程序而言,文件验证失败可能根本不是异常,仅仅可能的结果之一。比方说支持 XML 的编辑器或者 IDE。在这些环境中,无效的 XML 不应该造成系统崩溃和关闭。另外,如果只能以 Exception 形式报告错误 ,就过于沉重了。

当然,对于 JAXP 老手这并不新鲜,您可能已经习惯为 SAXParserDocumentBuilder 提供 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 对象后,就可以将其赋给 SAXParserFactoryDocumentBuilderFactory,都通过 setSchema() 方法(参见清单 7)。


清单 7. 把验证集成到解析中
				// Load up the document
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Set up an XML Schema validator, using the supplied schema
Source 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 factory
factory.setSchema(schema);
// Parsers from this factory will automatically validate against the
//   associated schema
DocumentBuilder 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 赋给 SAXParserFactoryDocumentBuilderFactory 相比,似乎更可靠。建议您采用比较保险的办法。我并不是要求您避开这种 API,而是说应该使用尽可能多的样本文档,并对验证结果检查两次,对错误处理要小心谨慎。





回页首


结束语

坦白地说,JAXP 验证 API 并没有明显的新东西。可以继续使用 SAX 或 DOM 解析和验证 XML,并结合 SAX 的 ErrorHandler 类,通过巧妙的编程也能对验证错误进行即时处理。但是这需要对 SAX 有充分的了解,需要很多时间去测试和调试并且仔细地管理内存(如果最终创建 DOM Document 对象的话)。这正是 JAXP 验证 API 闪光的地方。它提供了一种经过认真测试的、可以随时使用的解决方案,而不仅仅是是否启用模式验证的一个开关。它很容易与已有的 JAXP 代码结合在一起,增加模式验证非常简单。我相信,长期使用 XML 的 Java 开发人员一定会发现 JAXP 验证的一些优点。



参考资料

学习

获得产品和技术
  • Java 2 Platform Standard Edition 5.0:如果不熟悉 Java 编程,可以下载 JAXP 和完整的 JDK。

  • Libxml2:Libxml2 是为 Gnome 项目开发的 XML C 解析器和工具箱。其中包括 xmllint 验证程序。


讨论


关于作者

作者照片

Brett McLaughlin 从 Log 时代就开始使用计算机了。(还记得那个小三角吗?)最近几年,他已经成为 Java 和 XML 社区最受欢迎的作者和程序员。他曾经在 Nextel Communications 实现过复杂的企业系统,在 Lutris Technologies 编写过应用程序服务器,最近又开始在 O'Reilly Media, Inc. 继续撰写和编辑这方面的图书。他的最新著作 Java 5.0 Tiger: A Developer's Notebook 是关于 Java 技术这一最新版本的第一部专著,经典著作 Java and XML 仍然是在 Java 语言中使用 XML 技术的权威著述之一。

你可能感兴趣的:(jaxp)