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
包中实现。
|
详细了解这种验证 API 的具体细节之前,必须充分了解 JAXP 1.3 之前验证是如何完成的。此外,显然 Sun 仍将支持过去的 DTD 验证方法,但是建议使用基于模式的新的验证 API。因此即便您义无反顾地要使用 javax.xml.validation
包,仍然需要理解使用 DTD 验证文档的方法。
在一般的 JAXP 处理中,都是从 工厂 开始的。SAXParserFactory
用于 SAX 解析,DocumentBuilderFactory
则用于 DOM 解析。这两种工厂都使用静态方法 newInstance()
创建,如清单 1 所示。
// Create a new SAX Parser factory SAXParserFactory factory = SAXParserFactory.newInstance(); // Create a new DOM Document Builder factory DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
|
虽然 SAXParserFactory
和 DocumentBuilderFactory
有分别适合 SAX 和 DOM 的不同特性和性质,但是对验证来说它们都有一个共同的方法:setValidating()
。如您所料,要打开验证,只需要把 true
传递给该方法。但是使用工厂是为了创建解析器而不是直接解析文档。创建工厂之后就可以调用 newSAXParser()
(SAX)或 newDocumentBuilder()
(DOM)。清单 2 显示了这两个方法,都打开了验证。
// 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 的对象(SAXParser
或 DocumentBuilder
)。但是要记住,这样做 仅 限于 DTD 解析。setValidating(true)
调用对基于 XML 的解析完全没有作用。
|
|
5 年前,用一个漂亮的方法打开 DTD 验证就足够了。甚至两年前,XML Schema 和 RELAX NG 之类的模式语言仍然在忙于解决自己的问题。但今天,用模式验证文档与 DTD 方式一样常见。这两种方法同时存在很大程度上是因为遗留文档仍然使用 DTD。今后几年内,DTD 将和 Lisp 一样消失,成为历史遗迹而不是主流技术。
JAXP 1.3 通过引入 javax.xml.validation
包支持模式验证已经在开发人员中引起了很大反响。这个包易于使用,结构紧凑,已经成为 Java 语言的标准组成部分。更好的是,如果您曾经通过 JAXP 使用过 SAX 和 DOM,那么掌握如何验证就更简单了。模型是类似的,您会发现使用这种 API 进行验证简直轻而易举。
通过 简要的历史回顾 您知道,使用 SAX 的第一步是创建新的 SAXParserFactory
。如果使用 DOM 则首先创建 DocumentBuilderFactory
。因此毫不奇怪,进行模式验证首先要创建 SchemaFactory
类的实例,如清单 3 所示。
import javax.xml.XMLConstants; import javax.xml.validation.SchemaFactory; ... SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_SCHEMA_NS_URI); |
这和其他工厂的创建类似,只不过增加了传递给 newInstance()
方法的参数。必须向该方法传递另一个类中定义的常量,即 javax.xml.XMLConstants
类,对这个类也需要非常熟悉。这个类定义了 JAXP 应用程序中使用的所有常量,不过现在只需要知道两个:
XMLConstants.RELAXNG_NS_URI
XMLConstants.W3C_XML_SCHEMA_NS_URI
因为 SchemaFactory
是与具体的约束模型联系在一起的,所以必须在工厂构造的时候提供这个值。
SchemaFactory
类还有其他几个选项。这些内容在后面的 深入了解验证 一节中再介绍。对于一般的 XML 验证,预设的工厂就够了。
|
建立工厂后还需要装入需要使用的约束集。可以通过工厂的 newSchema()
方法来完成。但是工厂以 javax.xml.transform.Source
实现作为输入,因此需要一个中间步骤:将模式转化成 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
中的数据,包括使用 SAX 或 DOM(分别通过 SAXSource
和 DOMSource
)读取约束,甚至使用 URL。
一旦得到了 Source
实现,就将其传递给工厂的 newSchema()
方法。返回的就是 Schema
。现在,对文档进行验证就很简单了。请参阅清单 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")); |
这里同样没有什么大的变化。只要知道要使用的类和调用的方法就很容易了。因为要进行验证,所以必须使用 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 提供了一个简单的例子。
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 一样,可以使用该接口自定义错误的处理。从而让应用程序从容地退出验证、打印错误消息,甚至可以尝试从错误中恢复并继续验证。如果熟悉这个接口,完全不需要再重新学习!
|
某些很少见的情况下,可能需要从多个模式构造 Schema
对象。这有点儿费解;一个 Schema
不是 对应一个模式或文件。相反,该对象表示一组约束。这些约束可以来一个文件,也可以来自多个文件。因此,可以通过 newSchema(Source[] sourceList)
为 newSchema()
方法提供一个 Source
实现数组(表示多个约束)。返回的仍然是一个 Schema
对象,表示所提供的模式的组合。
可以预料,这种情况下会出现很多错误。因此建议为 SchemaFactory
设置 ErrorHandler
(更多信息参见 处理错误 一节)。很多地方都可能出问题,因此要准备好在出现的时候解决问题。
到目前为止,我们一直把验证作为独立于解析的单独部分。但是并非必须如此。得到 Schema
对象后,就可以将其赋给 SAXParserFactory
或 DocumentBuilderFactory
,都通过 setSchema()
方法(参见清单 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
赋给 SAXParserFactory
或 DocumentBuilderFactory
相比,似乎更可靠。建议您采用比较保险的办法。我并不是要求您避开这种 API,而是说应该使用尽可能多的样本文档,并对验证结果检查两次,对错误处理要小心谨慎。
|
|
坦白地说,JAXP 验证 API 并没有明显的新东西。可以继续使用 SAX 或 DOM 解析和验证 XML,并结合 SAX 的 ErrorHandler
类,通过巧妙的编程也能对验证错误进行即时处理。但是这需要对 SAX 有充分的了解,需要很多时间去测试和调试并且仔细地管理内存(如果最终创建 DOM Document
对象的话)。这正是 JAXP 验证 API 闪光的地方。它提供了一种经过认真测试的、可以随时使用的解决方案,而不仅仅是是否启用模式验证的一个开关。它很容易与已有的 JAXP 代码结合在一起,增加模式验证非常简单。我相信,长期使用 XML 的 Java 开发人员一定会发现 JAXP 验证的一些优点。
ErrorHandler
接口。 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 技术的权威著述之一。 |