Java 中的 XML: 数据绑定

来源:百度文库 编辑:神马文学网 时间:2024/04/27 14:17:14
Java 中的 XML: 数据绑定,第 1 部分:代码生成方法 — JAXB 及其它
根据 DTD 或模式生成数据类
Dennis M. Sosnoski
总裁, Sosnoski Software Solutions,Inc.
2003 年 6 月
企业 Java 专家 Dennis Sosnoski 研究了几种 XML 数据绑定方法,这些方法根据用于 XML 文档的 W3C XML Schema 或 DTD 文法来生成代码。他从人们期待已久的 JAXB 标准(马上就要由 Java Community Process,JCP 发布了)入手,然后总结了其它一些目前可用的框架。最后,他讨论了如何以及何时以最佳方式将依据文法的代码生成应用到应用程序中。
数据绑定 提供了一种简单而直接的方法,以在 Java 平台应用程序中使用 XML。有了数据绑定,应用程序可以在很大程度上忽略 XML 文档的实际结构,而直接使用那些文档的数据内容。虽然这种方法不能适合于所有应用程序,但在一般情况下,对于那些将 XML 用于数据交换的应用程序是比较理想的。
除了简化编程之外,数据绑定还提供了其它一些好处。由于数据绑定对许多文档细节进行了抽象,因此对于在内存中处理文档,它通常所需要的内存比文档模型方法(譬如 DOM 或 JDOM)要少。您还会发现,由于不需要遍历文档结构以获取数据,因此用数据绑定方法访问程序内的数据要比用文档模型方法快。最后,在输入时,一些特殊类型的数据(譬如数字和日期)可以被转换成内部表示,而不是保留为文本形式;这使应用程序可以更有效地使用数据值。
您可能想知道,如果数据绑定有这么多的好处,那么何时使用文档模型方法呢?基本上在以下两种主要情形下需要用文档模型方法:
当应用程序确实要关注文档结构的细节时。例如,如果您正在编写一个 XML 文档编辑器,则您会希望采用文档模型,而不是使用数据绑定。 当正在处理的文档不需要遵循固定的结构时。例如,对于实现常规的 XML 文档数据库,数据绑定不是一种很好的方法。
去年,我写了一篇文章,讲述了如何使用 Castor 框架以进行 Java 对象到 XML 文档的映射数据绑定。我曾经答应要写一篇后续文章,其中将探讨代码生成方法,包括介绍 JAXB,Java Community Process(JCP)正在开发 JAXB,它是用 Java 语言编写的、用于数据绑定的标准 API。就在较早的那篇文章发布不久,Sun 宣布对 JAXB 的方向做出了重大调整(请参阅重新架构 JAXB)。由于这方面的变化,所以我想,为了更贴近最终的 JAXB 代码,最好先不写这篇后续文章,现在,我很高兴,终于可以写这篇文章了!
下面是一个微型字典,里面包含了我在本文中所使用的一些术语:
文法(Grammar) 是用于定义一系列 XML 文档结构的一套规则。其中一类文法是 XML 规范所定义的文档类型定义(Document Type Definition,DTD)格式。另一类日渐普及的文法是 XML Schema 规范所定义的 W3C XML Schema(Schema)格式。文法定义了哪些元素和属性可以出现在文档中,以及在文档中元素是如何嵌套的(通常包括嵌套元素的次序和数目)。一些类型的文法(譬如 Schema)还可以更进一步,使字符数据内容与特定数据类型甚至正则表达式相匹配。在本文中,我会常使用术语 描述, 将它作为引用一系列文档的文法的非正式方法。
编组(Marshalling)是在内存中为对象生成 XML 表示的过程。与 Java 对象序列化一样,这种表示需要包含所有依赖的对象:我们的主对象引用的对象、这些对象引用的对象等等。
数据分解(Unmarshalling) 是与编组相反的过程,在内存中根据 XML 表示构建一个对象(而且可能是链接对象的图)。
在本文中,我将讨论根据 XML 文档文法生成 Java 语言代码的五种 XML 数据绑定框架:JAXB、Castor、JBind、Quick 和 Zeus。它们都可以免费获取,除了 JAXB 之外,其它四种框架都可以在开放源码和专利项目中使用。当前 JAXB 参考实现 beta 测试版的许可证只允许用于评估,但当它作为产品发行时,这种情形很可能会改变。JAXB、Castor 和 JBind 都提供了根据 XML 文档的 Schema 描述生成代码,而 Quick 和 Zeus 根据 DTD 描述生成代码。Castor 和 Quick 还支持将现有类映射到 XML,以此作为另一种生成代码的方法。
这些框架各有优缺点,所以我会试图逐步指出每种框架所具有的最佳(和最差)特性。在第 2 部分,我将进一步向您显示这些框架如何对一些样本文档进行处理,另外,还将探讨,对于许多类型的应用程序,现有的数据绑定框架怎么会缺乏一些重要的特性。
相对于我在以前文章中所描述的映射绑定方法,根据 Schema 或 DTD 文法生成 Java 语言代码具有一些突出的优点。使用生成的代码,您可以确定数据对象被正确地链接到 XML 文档,不象映射绑定方法,需要直接指定链接,并确保正确地涵盖了所有的结构变体。在使用 Schema 时,甚至可以利用文法所提供的类型信息,用合适的数据类型来生成代码。
代码生成方法也有一些不足之处。这种方法造成应用程序数据结构与 XML 文档结构之间紧密耦合。另外,它还可能限定您使用简单的数据类(没有关联行为的被动数据容器),而不是真正的对象类,在编组和数据分解过程中,还可能限制应用数据的定制转换的灵活性。在本文后面,我会权衡代码生成和映射绑定这两种方法(请参阅映射绑定 vs. 代码生成)。
对于将在第 2 部分中讨论的性能测试,我用每一种数据绑定框架来生成代码。用于性能测试的文档包含模拟航班时刻表的信息。下面是一个样本文档,您可以感受一下其中的结构:
9 http://www.arcticairlines.com Arctic Airlines 7 http://www.combinedlines.com Combined Airlines Seattle, WA Seattle-Tacoma International Airport Los Angeles, CA Los Angeles International Airport 426 6:23a 8:42a 833 8:10a 10:52a 433 9:00a 11:36a 311 7:45a 10:20a 593 9:27a 12:04p 102 12:30p 3:07p
图 1 显示了用于映射数据绑定到这些文档的类结构。为了进行比较,我将在有关各个数据绑定框架章节中显示生成的类结构。这里包含的这些图仅仅是所有情况的缩略图;如果要看全图,请单击这个小图像。
图 1. 映射绑定类图(单击进行放大)

用于 XML 绑定的 Java API(Java API for XML Binding,JAXB)是一个处于不断发展中的 Java 平台数据绑定标准。Java Community Process 正在开发作为“JSR-31 ― XML 数据绑定规范(XML Data Binding Specification)”的 JAXB。该项目始于 1999 年 8 月,其目的是定义一种方法,生成与 XML 结构相链接的 Java 语言代码。最初打算在 2000 年第 2 季度发布,但最后在 JavaOne 2001 上宣布了初步的 Early Access(EA)版本,该版本在 2001 年 6 月向公众发布。
JAXB 的 EA 版本基于具有创新意义的拉解析器(pull parser)设计,这种设计使验证可以方便地构建到生成的数据分解代码中。它根据 DTD 生成代码,构建在解析 XML 文档时自动验证 XML 文档结构(而不是数据)的类。我们期望这种方法能快速和有效地处理 XML 和 Java 语言对象之间的转换,但 EA 代码仅仅是部分实现,显然在成为完整的实现之前,仍需要做大量工作。
专家组不久之后开始收到关于 EA 发行版的反馈。作为对反馈意见的部分响应中,他们研究决定重新架构 JAXB,之后更新了网站,声明 JAXB 在几个方面正在得到增强。该站点还声明,下一版本在 API 级上不与早期版本兼容 ― 但您仍然可以下载 EA 版本。
直到 2002 年 3 月,新体系结构的细节才公布于众,在 JavaOne 上,Sun 宣布,作为在 JAXB 方面进一步工作的基础,实际上正在放弃 EA 代码。它将被新设计所替代,在新设计中,共享了一些常见的功能,但新设计使用不兼容的 API 和内部体系结构。发展方向变化如此之大,让我和那些对 EA 代码有兴趣的人们感到惊讶。
JAXB 项目的 SUN JSR 负责人 Joseph Fialli 把这么大的变化归结为以下一些因素。主要问题是扩展原有代码库以支持 W3C XML Schema 的复杂性。这是一个相当复杂的规范,以至于在批准之后的两年多时间里,在所有平台上,仍然只有少数几个解析器能接近完全符合规范。最初的 JAXB 代码需要实际对象来控制验证,而且将这种方法扩展到 Schema 将耗费太多精力,以至于在合理的期限内无法实现这项工作。
为适应 Schema 而做出变更的同时,专家组还决定重新考虑处理验证的方法。原来的 JAXB 代码无条件地验证文档的结构,如果发现错误,则抛出异常并中止处理。Fialli 说,在公众的意见中,抱怨这种方法局限性太大、限制太严 ― 在一些情况下,用户希望能够一次检查多个验证错误,而在另一些情况下,则希望完全禁用验证(或者由于性能原因,或者在编组没有精确匹配文法的文档时)。新的 JAXB 体系结构能够满足这两种需求。
最后,专家组决定放弃单个绑定框架运行时(就象在原来 EA 发行版中所看的)的想法。而采用接口方法,其实质是可以使用不同的数据绑定框架。这使用户代码可以在各框架之间进行移植,而不需移植生成的类 ― 这些类特定于专门的数据绑定框架,它们只能由该框架运行。强制性的 SAX 2.0 解析器支持替代了 EA 运行时中所使用的拉解析器方法,对于其它解析器(可能包括新的拉解析器,该解析器基于用于 XML 的流式 API,JSR 173 正在定义此 API),提供可选的特定于框架的支持,数据结构本身被更改为类 JavaBean 的数据对象,外部框架可以方便地操作此数据对象。
自 3 月以来,JAXB 项目一直在朝着这个新方向前进。这一工作的第一个公开成果是,去年夏天发布了该规范的新草案(但草案仍处于准备阶段)。接着在 10 月,Sun 提供了 JAXB 参考实现新的 beta 测试版,最终它会替代过时已久的 EA 版本。在这些文章中,我用这个最近的 beta 测试版进行评估和性能测试。它直接根据 Schema 文档描述进行工作,生成与为文档所定义的元素类型和用法相匹配的类的层次结构。该层次结构包括四种常规类型的类:用于已定义类型的接口、用于实际元素的接口和这两组接口的实现。
图 2. JAXB 接口类图(单击进行放大)

图 3. JAXB 实现类图(单击进行放大)

从应用程序的角度来看,类型接口是这些类最有趣的部分。对于类型内的数据,存在着 JavaBean 样式的 get 方法和 set 方法集合。包含在类型接口中的这些方法遵循 JAXB 规范所规定的规则,所以应用程序代码可以安全地使用这些接口来访问所有数据,同时还保持了 JAXB 实现间的可移植性。这些接口使 JAXB 生成的代码可以相当容易地与现有文档一起使用。然而,构造和修改数据结构有点困难。因为使用接口,所以不能直接构造实例;而是必须使用工厂方法创建实例,然后使用类 JavaBean 的取值方法来填充数据值。
用 JAXB 根据 Schema 描述生成代码非常简单。所提供的绑定编译器 xjc 是一个命令行工具。它将 Schema 文档作为输入,将文件生成到指定的输出包和目标目录。其中所具有的选项还使用户可以控制生成的代码文件是否是只读的,以及是否严格验证 Schema 描述。
通过使用绑定声明,JAXB 规范定义了一些方法来定制生成数据绑定的一些方面。包括:
用于控制所生成类的名称和属性的选项 指定由绑定所使用的现有实现类的方法 允许(有限地)控制验证处理和用于编组和数据分解的序列化器/反序列化器(serializer/deserializer)的选项
要么在实际的 Schema 文档中以注释形式嵌入这些定制,要么通过使用单独的外部绑定声明文档来单独提供这些定制。参考实现的当前 beta 测试版只支持第一种方法,但在以后的发行版中将支持使用外部绑定声明文档。
总体说来,JAXB 正成为一种功能强大而灵活的工具,它用于将 Java 语言代码绑定到 W3C XML Schema 文法所定义的文档。由于有可能批准将 JAXB 作为一个 Java 平台标准,因此它将会受到广泛支持,而且在各实现之间移动绑定应用程序会象在 servlet 引擎之间移动 Web 应用程序一样容易(一般来讲,很简单,但偶尔也会有一些波折)。
然而,JAXB 确实也有一些缺点。目前最大的局限是只有用于评估用途的许可证。在该产品发行版(目前计划在这个季度发布)之前,JAXB 还无法用于实际项目中。另外,定制的程度也局限于只能应用到生成的代码。在许多情形中,您可以为 JAXB 所定义的接口定义自己的实现类,但这些接口本身总是与 Schema 描述联系在一起,不太可能进行修改。
用于 XML 数据绑定的 Castor 框架支持映射绑定和生成绑定。在我的上一篇文章中,我讨论了 Castor 映射绑定方法的一些特性。对于本文,我只讨论根据 Schema 生成代码,但在第 2 部分,我将研究这两种方法的性能。请回顾以前的文章(请参阅参考资料),了解有关 Castor 中映射数据绑定工作方式的更多信息。
图 4. Castor 生成的类图(单击进行放大)

在一些细节上,Castor 的代码生成支持不同于 JAXB 方法,但在目的上,两者非常相似。与使用 JAXB 一样,Castor 向应用程序提供类 JavaBean 结构的数据模型。主要差别在于 Castor 避免使用接口,而是喜欢直接使用生成的实现类。除了每个实现类,Castor 还生成描述符(descriptor)类,该类包含绑定和验证代码。由于 Castor 使用具体的类,而不是接口,因此对于构造或修改文档数据结构,它要比 JAXB 略微简单。可以仅仅直接使用相应类的构造函数,而不用通过工厂类。
Castor 的当前 beta 测试版(我写这篇文章时,该版本为 0.9.4.1)不支持在代码生成中进行任何实质的定制,但这种情况有望得到改变。下一 beta 测试发行版预计将支持使用映射文件来控制代码生成的各个方面。起初,在这些方面中,只支持类名和包名,但从更长远来看,计划将添加对用户所提供的实现类的支持。Castor 开发人员还计划在 Castor 中支持 JAXB,可能是通过使用某类兼容性层来实现这一点。
用 Castor 根据 Schema 描述生成代码与用 JAXB 一样方便,使用的基本选项也一样。Castor 确实使用一些附加的命令行参数选项,而且通过属性文件设置,甚至提供了更多选项。这些选项主要用在一些特殊的情形中,但不包括象 JAXB 那样通过 Schema 文档注释提供对类名和验证的控制。
现在,用 Castor 来生成源代码这种方法的主要缺点是对定制的支持有限。这种情形正在开始发生转变,可以用 Castor 的映射数据绑定方法来实现实质的定制(见前一篇文章中的描述 ― 请参阅参考资料),我期望最终在定制方面至少与源代码生成方法具有同样的灵活性。从长远来看,这将使它的适应性比 JAXB 更强。
Castor 按照 BSD 样式的许可证进行发布,完全可用于商业用途,而没有什么重大限制。它看起来相当稳定,但每当遇到需要修正错误时,您将需要更新到最新的开发代码(或等待新的 beta 测试发行版)。
与 JAXB 和 Castor 类似,JBind 根据 XML 文档的 Schema 描述来生成绑定代码。尽管具有这种相同的性质,但实际上 JBind 的着重点与前两个大不相同。JBind 的主要创建者 Stefan Wachter 称此着重点为“XML 代码”,他是这样描述它的:它将由 Schema 所描述的 XML 数据和由 Java 语言代码所实现的行为组合在了一起。JAXB 和 Castor 更多地着重于使 Java 语言应用程序方便地使用 XML,而 JBind 是围绕 XML 构建应用程序代码框架。一旦 JBind 构建好框架,则可以用自己的代码扩展它来添加功能。
图 5. JBind 生成的类图(单击进行放大)

JBind 还 可以 用于常规的数据绑定,在第 2 部分所讨论的性能测试中,我就是以这种方式用 JBind 的。但这样做略微有点笨拙,部分原因是由于 JBind 总是需要在运行时处理文档的 Schema。如果实例文档不直接引用相应的 Schema,则需要使用特殊的映射文件,或在读取实例文档之前,用手工将正确的 Schema 装入到自己的代码。目前的文档不会真正向您显示这是如何做的。与其它数据绑定框架相比,对处理绑定文档结构的更改,JBind 也很严格。通过使用 ListIterator ,可以删除现有的元素对象,但只有使用生成的 create(创建)方法才能创建新的元素对象,这些方法自动地将这些元素对象添加到现有内容的后面。
实质上,JBind 采用与前面框架大不相同的方法来处理文档数据。JBind 不生成 JavaBean 样式的数据类(但 JAXB 和 Castor 是这样做的),而是将一切存储在文档模型(目前为 DOM 级别 2 实现)中,构建绑定代码做为前端(facade)来访问存储在文档模型中的数据。这是一种非常有趣的方法,如果完全实现,这可能具有一些不错的跨范例好处。目前这种方法所具有的唯一好处是在生成代码中 。由于存储机制相对于 JBind 的主旨是次要的,因此将来这种机制还可能会有所变动。
JBind 所具有的好处是,在考虑过的所有数据绑定框架中,它支持 Schema 最彻底,并且提供上面 所说的 XPath 扩展。如果应用程序的核心是处理 XML 文档,则使用由 JBind 构造的“XML 代码”框架可能非常简单。对于一般的数据绑定用法,如果应用程序涉及到 XML 文档,而不是其重点时,则其它数据绑定方法可能会更简单些。由于数据分解时需要验证以及由于文档模型后端存储机制(我将在第 2 部分更详细地讲述此问题),因此与其它框架相比,JBind 还存在明显的性能劣势。JBind 是按照 Apache 样式的许可证分发的,完全可用于商业用途。
Quick 文档将自身描述为:不是作为处理 XML 的工具,而是作为对使用 XML 的 Java 语言的 扩展 。它基于位于 Java 平台和 XML 之前的一系列开发成果,在此过程中进行了大量的重构工作。它确实为在 Java 平台上使用 XML 提供了非常灵活的框架 ― 它所具有的灵活性远远超出了为写本文我所能够了解和使用到的。
图 6. Quick 生成的类图(单击进行放大)

Quick 的灵活性是有代价的。它使用一系列相当复杂的步骤来根据 DTD 文档描述移到生成的代码,在此过程中使用了作为中间步骤的三个独立的绑定模式(不要与 W3C XML Schema 混淆)文档:
QDML 文档提供文档描述,它大致相当于 DTD,不过添加了一些类型和继承。 QJML 文档定义了 XML 到 Java 语言对象的绑定。 QIML 文件基本上是 QJML 的编译形式,可以用它来生成实际的绑定代码。
在第 2 部分 Quick 的性能测试中,我尽可能少地定制这些文件,但为了得到预期的最终结果,仍然需要做一些手工编辑。根据 DTD 文法生成 QDML 文件之后,必须编辑该文件来定义文档的根元素,并为非 String 值(在这里,是几个 int )添加类型信息。然后,运行程序来从 QDML 生成 QJML 文件,并编辑生成的 QJML,从而向引用添加类型信息。其实,并不真正需要这一步,但有了这一步,就可以用针对对象引用的特定类型生成代码(Castor 和 JAXB 代码生成不支持该特性)。最后,运行该工具以从 QJML 生成 QIML 文件,然后运行代码生成工具完成整个过程,从而获得类 JavaBean 的对象类和实际的绑定类(用来从 XML 转换到 Java 类以及从 Java 类转换到 XML)。
再对这些文件进行一些手工编辑,则可以避免生成用于该对象类的新代码,直接链接到 Castor 映射绑定所使用的现有的类。这种可以使用现有类的能力是一项功能非常强大的特性。由于模式文件很复杂,而且为了利用该特性必须做大量的更改,这些因素稍微降低了 Quick 的实用性,但这确实展示了 Quick 的灵活性。
灵活性是 Quick 最强大的特性。使用 Quick 的主要缺点是各种模式文件的复杂性,以及缺乏对 Schema 文法的支持。另外,使用 Quick 时,获得帮助看来也很困难:在论坛和邮件列表中,提出的问题常常得不到任何响应。Quick 的许可证遵循 GNU 库(GNU Library)或次通用公共许可证(Lessor General Public License,LGPL),这种许可证允许自由项目和商业项目使用该软件。
象 Quick 一样,Zeus 也根据 XML 文档的 DTD 描述生成代码。(现在正在开发对 Schema 的支持,但目前处在开发的 pre-alpha 阶段)。这两种框架只在这个方面是相似的。Quick 复杂而功能强大,而 Zeus 易于使用 ― 但功能非常有限。
图 7. Zeus 生成的类图(单击进行放大)

在用法上,Zeus 代码生成类似于 JAXB 或 Castor,它提供了命令行工具来构造所需要的类。与使用 JAXB 一样,绑定使用接口。JAXB 使用工厂来构造对象类的新实例,而 Zeus 通过原型使用生成的实现类。用 Zeus 可以生成实现类的子类,当对文档进行数据分解时,使用子类而不是使用生成类。
不象前面所讨论过的其它任何框架,Zeus 只支持 String ,而不支持其它类型的值,譬如 int 或 Date 。它还缺乏对引用的支持,所以不能直接进行数据分解或编组图结构。这存在很大的局限性 ― 由于数据绑定可以利用类型数据值和对象间链接的透明处理所提供的便利性,因此这给数据绑定带来许多实用性。没有这些特性的支持,Zeus 更象是一种经过裁减的文档模型,而不象是一个完整的数据绑定框架。
如果只使用 String 值,则考虑使用 Zeus 可能是不错的选择。使用 Zeus 的主要缺点是,提供的绑定形式有限,从整体上看,该项目进展缓慢。与使用 Quick 一样,您可能会发现难以找到问题的答案。Zeus 按照 Enhydra 公共许可证 V1.1 进行分发,该许可证来自 Mozilla 公共许可证。
在本文中,我讨论了几种不同的框架,这些框架用于根据 XML 文档文法生成 Java 语言代码。这只是处理用于 Java 语言应用程序的 XML 数据绑定的一种方法。另一种主要方法是使用某种形式的映射绑定方法,在映射绑定方法中,构建自己的类(或者最初根据文法来构建类,然后修改它们以满足您的要求),并向绑定框架指定这些类如何与 XML 文档相关联。每种方法有其利弊,它们都可能有最合适的用武之地。
代码生成自动构建反映 XML 文档结构(换句话说,是 DTD 或 Schema 形式的文法)的类,这使您可以非常快速地开始使用文档。当代码生成基于 Schema 描述时,所构造的类可以包括完整的数据类型信息(尽管用此方法存在一些问题;许多 Schema 数据类型在 Java 语言中没有直接对应的数据类型)。用代码生成,您还可以将验证构建进所构造的类中,从而要么自动检查值(当设置好它们时),要么按照需要检查有效性。因此,您可以确保通过编组所生成的文档总是与所期望的结构相匹配。
代码生成方法的主要缺点是相对于其优点而言。通过如此贴实地反映文档结构,该方法使应用程序代码和文档结构之间紧密耦合。如果文档结构发生变化,再需要重新生成代码,并修改应用程序代码以使用最终修改后的数据类。
通常您还需要使用整个文档结构,为了生成代码,不易于对整个结构划分子集。如果正在使用带有许多可选组件的复杂结构(也许作为业界标准而定义的),而应用程序使用的文档只用这些组件中的某个子集,则这可能会有问题。对于这些框架中的大多数,使用它们时所生成的类将总是与整个文档结构匹配。您可能还需要在运行时包含所有这些生成的类,这取决于所使用的框架。对于应用程序,这导致代码过度膨胀以及数据模型过度复杂。当然,通过编辑 DTD 或者 Schema 以消除不需要的组件,您可以避免这种情况 ― 但随之而来当基本文法发生任何变化时,都需要维护您的修改,这又带来一组新的问题。
映射绑定(譬如由 Castor 或 Quick 实现的映射绑定)比代码生成具有更大的灵活性。使用真正的对象类将数据和行为组合在一起。也可以在一定程度上解除对象类与实际 XML 之间的耦合。修改映射定义(而不需要改变应用程序代码)通常处理 XML 文档结构中微小的变化。甚至可以用一种格式定义单独的输入和输出映射来对文档进行数据分解,并用另一种格式编组它们。映射绑定的缺点是,在设置方面,它确实比生成代码方法需要花费更多精力。
总之,对于所有应用程序,没有一种方法总是最佳的。如果使用由 Schema 或 DTD 定义的稳定文档结构,并且该结构适合应用程序的需要,则代码生成方法可能是最佳方法。如果使用现有的 Java 语言类,或者希望使用类的结构,该结构反映应用程序对数据用法,而不是 XML 文档结构,则映射方法可能最佳。遗憾的是,当前大多数的开发工作主要集中在代码生成而不是映射。这种局限导致目前只有 Castor 和 Quick 才有这种映射方法。
在这第 1 部分中,我回顾了几种主要的 XML 数据绑定框架,这些框架支持根据 XML 文档描述生成 Java 语言代码。这些框架在能力方面区别很大(在性能方面亦是如此,我将在第 2 部分中讨论此问题)。在基于 W3C XML Schema 定义的方法中,对于通用的数据绑定应用程序,Castor 提供了当前最佳支持。现在已经可以使用 Castor,目前人们正在扩展它以提供更好的生成代码定制功能和更完善的 Schema 支持。人们还期望 Castor 将来支持即将出现的 JAXB 标准。
一旦 JAXB 作为产品发行版使用(当前的 beta 测试版许可证仅允许用于评估),则它看起来将是一项非常吸引人的选择。目前 Castor 似乎比 JAXB 支持更多定制,但 JAXB 将提供可在各实现间进行移植的好处。JAXB 也很可能被用在其它 Java 平台标准(譬如用于 Web 服务的 JAX-RPC 标准)中,所以编写 JAXB 的应用程序在将来可能在插件上兼容这些标准。
JBind 提供了最佳的 Schema 支持。如果应用程序适合“XML 代码”模型,并且对性能的需求也不迫切,则 JBind 可能是很好的选择,但对于使用一般的 XML 数据绑定,它显得比较笨拙。Quick 提供了非常大的灵活性且功能强大,但它只支持 DTD 文法,而且使用起来相当复杂。Zeus 简单且容易,但它(与 Quick 一样)只支持 DTD,另外,只允许访问作为 String 值的数据。
最后三个框架似乎更适合具有特殊需求的应用程序,而非一般用途。如果文档只有 DTD 描述,而没有模式,则出于这个原因,您可能希望尝试 Quick 或 Zeus。对于大多数应用程序,这不是主要关心的问题,因为有许多可用于将 DTD 转换到模式的应用程序(尽管可能需要一些手工调整)。在 Castor 分发版中包括了一个这样的程序(正如在 Castor XML FAQ 中所提到的, org.exolab.castor.xml.dtd.Converter )。
在第 2 部分中,我将展示这些数据绑定框架的性能结果,这些性能是在一些样本文档上使用这些框架测试出的。这些结果涉及了生成代码方法和映射绑定方法,为了进行比较,包含了文档模型。我确实惊讶地看到……,但等等,现在我不能把 一切都告诉您。另外提醒您,在下个星期别错过这个完全关于性能的“故事” ― 我保证您会找到值得一读的结果!
Java 中的 XML: 数据绑定,第 2 部分:性能
经过了第 1 部分对数据绑定框架的介绍后,现在对其进行测试
级别: 中级
Dennis M. Sosnoski
总裁, SosnoskiSoftware Solutions,Inc.
2003 年 6 月
企业 Java 专家 Dennis Sosnoski 研究了 Java 中用于 XML 数据绑定的几种框架的速度和内存使用情况。这些框架包含第 1 部分中讨论的所有代码生成方法、更早的一篇文章中讨论的 Castor 映射绑定方法和一种令人惊讶的有可能成功的新方法。如果您正在您的 Java 应用程序中使用 XML,那么您会希望了解如何将这些数据绑定方法结合在一起!
第 1 部分介绍了有关为什么您希望对 XML 使用数据绑定的背景知识,还概述了可用于数据绑定的 Java 框架。如果您尚未阅读第 1 部分,那么现在您也许至少应该浏览一下那篇文章。在本部分中,我将直接讨论性能问题,而不会进一步讨论原因和方法!
为了对数据绑定框架进行性能测试,我生成了包含模拟的航班时刻表信息的文档。这些文档的结构与我在较早的有关利用 Castor 进行映射数据绑定的文章(请参阅参考资料)中定义的结构相同。下面是该结构的样本,之所以称其为 紧凑格式是因为它主要对数据使用了属性:
http://www.arcticairlines.comArctic Airlineshttp://www.combinedlines.comCombined AirlinesSeattle, WASeattle-Tacoma InternationalAirportLos Angeles, CALos Angeles InternationalAirport
注:清单 1中的机场名称信息通常是一行代码。为了适应列大小,一些代码行被拆开,出现在两行上。
除了紧凑格式外,我还尝试了一个变体,它的数据值更多地使用了子元素(只对 ID 和 IDREF 继续使用属性)。下面是用在此被称为 完整格式的格式表示的同一个数据:
9http://www.arcticairlines.comArctic Airlines7http://www.combinedlines.comCombined AirlinesSeattle, WASeattle-Tacoma International AirportLos Angeles, CALos Angeles International Airport4266:23a8:42a8338:10a10:52a4339:00a11:36a3117:45a10:20a5939:27a12:04p10212:30p3:07p
通常,根据所用文档的大小不同,XML 框架的相对性能会有巨大差异,因此在这些性能测试中,我同时包含了大文档和小文档。大文档( time-comp.xml和 time-full.xml)使用相同的数据值(分别以如上所示的两种不同格式表示)。因此大小明显不同(紧凑格式的为 106 KB,而完整格式的为 211 KB)。小文档都在集合中,每个集合包含 34 个文档,紧凑格式( ttcomp)的大小从 1.4-3.3 KB 不等,完整格式( ttfull)的大小从 2.2-5.8 KB 不等。与大文档一样,小文档集合中的相应文档包含相同的数据值。可以从下载页面(请参阅参考资料)获得测试中使用的完整文档集。
下面是我在本文中使用的一些术语的一个袖珍字典:
编组(Marshalling)是在内存中为对象生成 XML 表示的过程。与 Java 对象序列化一样,该表示需要包含所有从属对象:我们的主对象引用的那些对象,以及那些对象引用的对象等等。
数据分解(Unmarshalling)是编组的逆过程,它根据 XML 表示在内存中构建对象(可能还有一幅链接对象的图)。
映射(Mapping)是一组规则,用于显式地将对象编组到 XML 文档和根据 XML 文档分解对象。使用代码生成(基于文档的 DTD 或 W3C XML Schema 描述)的数据绑定方法通常包含隐式的映射,这些映射内置在已构造的对象中,因此在本文中,术语 映射只用于将用户定义的 Java 对象与 XML 文档进行关联的方法。
我更希望这些结果能使用更多的文档变体而不只有两种格式来进行测试。但是,由于需要为代码生成提供 W3C XML Schema(Schema)和文档类型定义(Document Type Definition,DTD)描述,还要为映射版本提供映射文件和基类,因此为数据绑定测试添加更多文档所涉及的工作量是可观的。本文使用的两种格式(包含大文档和小文档变体)至少会相当具有代表性地说明,对于典型的业务文档,数据绑定备用方案是如何执行的。但是,由于这些文档中的大多数数据值可以转换成基本类型 ,所以它们可能会使映射绑定方法显示出,内存使用情况将好于典型的普通文档。这导致一种非常紧凑的内部表示。对于其中大多数数据值都需要保存为 String 的文档而言,映射绑定方法的内存优势就会被削弱。
所有测试结果都是使用 1.4GHz Athlon 系统(拥有 256MB DDR RAM,运行 RedHat Linux 7.2)获得的。在所有测试中,我都使用了 Sun 的 JDK 1.4.1 for Linux。所测试的每个数据绑定框架的特定版本如下:JAXB Beta 1、Castor 0.9.4.1、JBind 1.0 Beta 12/07、Quick 4.3.1 和 Zeus Beta 3.5(JiBX 是一个特例 — 请参阅测试结果后面的那么什么是 JiBX?以获取详细信息)。除了 JBind 和 JiBX 之外,所有测试都使用了 Piccolo SAX2 解析器 V1.0.3。这是我知道的最快的 SAX2 解析器,它通常可以达到或超出用于 JiBX 测试的 XMLPull 解析器(XPP3 V1.1.2)的速度。JBind 无法使用 Piccolo 解析器,因此为测试 JBind,我使用了 Xerces Java 2 V2.2.0。
为了提供数据绑定和其它备用方法之间的性能比较,我还只使用 SAX2 解析器对相同文件运行了计时测试,并且使用 dom4j 文档模型(文档模型中的性能佼佼者,它允许使用不同的 SAX2 解析器解析输入文档)运行了计时和内存测试。对于这些测试,我使用了 dom4j V1.3。
在这些计时和内存使用量测试中,我使用的基本框架与以前的文档模型测试(请在参考资料中参阅作者有关文档模型性能的文章。)中所用的相同。这个基准测试框架首先将所有文档读入内存缓冲区,然后对针对文档的输入和输出操作的多次传递进行计时。输入计时和输出计时中显示的测试结果是数次传递过程中的最佳计时。这应当代表了服务器类型的环境(其中重复执行相同的代码)中的长期性能。
图1和2显示了使用 dom4j 文档模型和各种数据绑定方法读取 XML 文档(就数据绑定而言,就是对其进行数据分解)以及构造内存中表示的计时结果。在这些图表中,您可以将第一个 SAX2 计时值作为解析文档的基本时间。文档模型和数据绑定实现使用该解析结果来构建其在内存中的表示,因此它们决不会比解析器本身快。标有说明的两个数据绑定测试基于映射而不是代码生成。


p>
dom4j 构造文档的内存表示所花费的时间不到单独使用解析器所花费时间的两倍。优于该性能的唯一数据绑定框架是 JiBX。与 dom4j 相比,JAXB、Quick 和 Zeus 都获得了不错的性能数字,但是所花费的时间整体来说都几乎是 JiBX 的两倍。比较起来,Castor 非常缓慢,使用映射绑定和生成代码都如此。
相对于这些测试中的大多数绑定框架,JBind 的执行速度慢了整整一个数量级。这样拙劣的性能一小部分原因是由于用于 JBind 测试的解析器比较慢(因为它无法使用其它测试所用的解析器)。更大的原因可能是由于 JBind 强制在输入时对照 Schema 进行文档验证,这样会增加大量开销。但是,导致这一拙劣性能的最主要原因可能是由于 JBind 框架本身,该框架使用非常间接的方法来进行绑定(在当前实现中,绑定建立在 DOM 文档模型之上)。
除了 JBind 以外的所有测试都是在不进行完全验证的情况下运行的。大多数数据绑定框架仅按照其设计包含某个固有的验证级别(例如,确保元素的内容模型是匹配的)。大多数框架还可以使用验证解析器(如 Xerces Java 2)在输入时对文档进行完全检查,并且有框架(包括 JAXB)可以在内存中执行绑定数据的完全验证。因为在这些测试中主要关心的是性能,所以我尽可能地禁用了可选验证(包括在 Castor 中使用属性文件和数据分组程序/编组程序设置)。
图3和4显示了使用 dom4j 和各种数据绑定方法生成内存中表示的 XML 文本序列(就数据绑定而言,就是对其进行编组)的计时结果。这些图表使用的纵坐标与前两幅图表相同,以使比较变得简化,但是区别在于没有与 SAX2 解析器数字相对应的数字。


在该领域中,dom4j 提供的性能是所有数据绑定方法中最好的,比 JiBX 稍好一点,比 Zeus 更加好一点。其它数据绑定框架都花费了约两倍的时间,Quick 是所有框架中最慢的(当然,不是故意在说双关语)。尽管这里的结果与输入测试的几乎没有太大的变化,但是 dom4j 的确优于其它任何数据绑定框架的这一事实表明它们仍然有改进的余地。
图5和6显示了性能情形的另一部分,研究了内存使用情况。当利用文档模型使用非常大的文档(通常有 5+ MB 大小)时,运行时内存的不足会成为一个问题。对数据绑定方法如何进行文档表示所使用的内存量的比较呢?


这里的差异比时间性能比较中的差异更大,并且表现出了一个非常不同的模式。尽管 dom4j 在时间测量中执行的很好,但是在内存使用方面,它比任何数据绑定框架(除了 JBind,它构建在与 dom4j 的表示相当的内部文档模型上)差远了。与该领域中最优秀的执行者相比,表示相同的数据,dom4j 所占用的内存是前者的 10 倍。
两种映射绑定方法为绑定数据使用了同一种内部结构,所以它们表现出了相同的内存使用情况。这让它们在内存效率的“竞技场”上并列第一,从而产生了比使用生成代码的数据绑定方法优越几倍的性能。部分原因是因为 映射绑定使用了数据值的紧凑表示。在这些测试中,映射绑定将大多数数据值转换成 int 值(在大多数 Java 虚拟机(Java Virtual Machine,JVM)中, String 即使只包含一个或两个字符,都将占用 20 个以上的字节,而 int 只占用 4 个字节)。该转换的开销增加了读写次数,但是除了只是内存大小减小了以外,它的确还有其它优点。当实际使用数据时, int 远比 String 更便利和有效。
映射绑定方法之所以能获得较高的内存效率,除了因为它更为广泛地使用了原语值外,另一个原因是生成代码方法通常会将控制信息添加到出现在每个绑定对象中的实际数据中去。该控制信息增加了对象的大小,因而数据绑定少了一个主要优点。
在这些测试中,使用生成代码的数据绑定框架消耗的内存至少是映射绑定的几倍,但是(除 JBind 外)仍然比 dom4j 的文档模型表示小很多。这一点不足为奇 — 诸如 dom4j 的文档模型需要构造一些对象以表示文档的每个组件(包括实际的数据文本以及诸如元素和属性之类的结构组件),而数据绑定只需要保存实际的数据。对于生成代码绑定而言,许多实际数据仍然是作为 String 存储的,但是一些值可以被转换成 int ,而其它值可被转换成对象引用。
这里,Zeus 被认为是唯一直接将所有数据存储为 String 的数据绑定方法,这使它成为常用的数据绑定方法中占用内存最大的一种方法。到目前为止,JBind 的内存使用情况仍然较大。这有一部分是由于它在内部使用了文档模型,但是 JBind 使用的内存量要比单独使用文档模型(如 dom4j)所需的内存量大好几倍。从该内存使用情况判断,似乎 JBind 创建了许多其它对象,以建立绑定虚包(facade)和文档模型中实际数据之间的链接。
图1到6说明了数据绑定框架在扩展的测试运行(代表了服务器环境)中执行结果如何。我认为,研究这些框架在仅执行一次(single-execution)环境(例如其中有一个应用程序正在使用数据绑定代码来读或写配置文件)中使用时的比较结果也很有趣。图 7 显示了结果。

图7显示了启动一个短文档所花费的时间 — 从基准测试程序开始执行到整个操作返回为止(将数据分解成对象,然后将对象编组回文档)。同前面的计时数字不同:这里大多数的时间花费在了类装入,以及为获得数据绑定框架代码而由 JVM 进行的本机代码生成。通过将这些结果与前面的计时图表进行比较,可以看到这一启动时间通常比实际处理时间(即使是处理相当大的文档)要大好几倍。如果您的程序每次执行时将只使用一些文档,那么该启动时间将是比前面显示的最佳情形时间更重要的因素。
数据绑定框架使用的 jar 文件的大小是影响这一启动时间的一个主要因素。JiBX 是最小的,运行时和解析器的总大小不足 60KB。JAXB、Castor 和 JBind 是最大的,每个大小大约为 1MB。该时间还受每个框架所需的初始化影响。在使用映射绑定的 Castor 情形中,该时间包含处理映射定义文件,而对于 JBind 而言,它包含处理文档的 Schema 定义。
既然我已经展示了性能结果,那么我可能应该介绍一下这个几乎在每项测试中都能占据小组中的第一名的框架。是的,事实上它是“作弊的参加者”— JiBX 是一种针对性能而设计的数据绑定框架,因此如果它满足了其设计需求,那么在这些测试中它 应该是最佳的执行者。
JiBX 实际上源于本系列文章。当我开始研究可用的数据绑定框架时,我惊奇地看到:与文档模型(如 dom4j)相比,它们并不是执行得都那样好。这与我的期望相反,因为数据绑定方法实际上减少了保存在内存中的文档信息的数量 — 而文档模型在内存中保存 所有事物,同时数据绑定只需要实际数据。我认为,数据用得少的方法通常应该比那些数据用得多的方法要快。
在研究现有数据绑定框架是如何操作的过程中,我发现从性能角度来说有两个方面看上去不是很好。第一个方面就是许多框架中广泛使用了反射。反射是在运行时访问有关 Java 语言类的信息的一种方法。可用它来访问类实例中的字段和方法,从而提供了一种在运行时将类动态地挂钩在一起,却无需类之间有任何源代码链接的方法。反射是一种功能非常强大的 Java 技术特性,但是将其与调用方法或直接访问已编译代码中的字段相比,它在性能上有些欠缺。
我质疑的第二个方面是使用 SAX2 解析器对文档进行数据分解。SAX2 是一种非常有用的解析 XML 的标准,但是其事件驱动方法并不非常适合数据绑定和类似的应用程序。这里的问题在于,处理 SAX2 事件的代码需要维护其处理的所有事情的状态信息,这既增加了复杂性又增加了开销。
我创建了形成 JiBX 的代码,以对一些方法(解决其它数据绑定框架中所存在的这些问题)进行测试,并实验扩展超出 Castor 支持范围的映射绑定方法。JiBX 使用字节代码增强而不是反射来在项目构建时将挂钩添加进应用程序代码。JiBX 基于拉(pull)解析器体系结构(当前是 XMLPull),而不是 SAX2。JiBX 不是根据 DTD 或 Schema 生成代码,而是使用绑定定义,该定义将用户提供的类与 XML 结构相关联。
这些技术并不是 JiBX 所特有的。许多 Java 数据对象(Java Data Object,JDO)实现都使用字节代码增强,基本上都是为了达到与 JiBX 相同的目的(将访问挂钩添加到现有的已编译代码中)。原始的 JAXB 代码(已被丢弃)基于类似 XMLPull 的拉解析器体系结构。Castor 和 Quick 都支持数据绑定的映射方法(尽管有一些限制)。即使个别技术不是很新,但是它们的组合仍然可以形成其它数据绑定框架非常有趣的备用方案。
在本系列文章的第 3 部分,我将完整地介绍有关 JiBX 的知识。JiBX 仍然处于初期开发阶段。为了性能测试,我手工编写了代码,通常通过字节代码增强添加该代码,并使用 JiBX 运行时的当时版本来运行它。到本文发表时,我仍在着手完成增强代码,有许多其它特性我希望添加到其中。如果您在第 3 部分发表前就希望了解更多有关 JiBX 的知识,请查阅参考资料以获取到 JiBX 站点的链接。您甚至可以为 JiBX 的未来开发献策献力,也可以在您自己的应用程序中使用 JiBX。
这篇对数据绑定性能的研究展示了一些有趣的结果,但是并未对第 1 部分中的推荐做根本上的更改。Castor 为使用代码生成(根据 W3C XML Schema 定义)的数据绑定提供了最佳的当前支持。与其它备用方案相比,它的数据分解性能比较差,但是它的确可以提供较好的内存利用率和相当快的启动时间。Castor 的开发人员说,在他们的 1.0 发布以前,他们计划专注于解决性能问题,所以到那个时候,您也可能会看到在数据分解性能方面的一些改进。
JAXB 看上去将来仍是代码生成方法的一个不错选择(测试版许可证只允许评估使用)。当前的参考实现测试版在 jar 大小方面非常庞大,并且在内存使用方面的效率也略嫌不足,但是这里再重申一次,将来您可能会看到更佳的性能。在撰写本文的时候,当前版本仍然是测试版,只有当它作为商业或开放源码项目发布以后,它的性能才可能优于参考实现。由于它将作为 J2EE 平台的标准部分,所以关于在 Java 中使用 XML 方面,JAXB 无疑会扮演重要的角色。
性能结果也证实:JBind、Quick 和 Zeus 最适用于有特殊需求的应用程序,而不应该用于一般用途。JBind 的 XML 代码方法可以为围绕 XML 文档处理而构建的应用程序提供重要基础,但是当前实现的性能容易导致问题。Quick 和 Zeus 提供根据 DTD 进行代码生成,但是正如我在第 1 部分中提到的那样,将 DTD 转换成 Schema 通常相当简单。缺点是,Quick 使用起来似乎过于复杂,而 Zeus 只支持 String 用于绑定数据值(没有原语或使用 ID-IDREF 或等价物的对象引用)。
对于数据绑定的映射方法,Castor 的优点是:它是一个相当稳定的实现,并可投入实际使用。Quick 也可以用于这类的绑定,但是似乎也难于设置。JiBX 是新事物,并且尚未完全投入使用,但是它提供了卓越的性能和高度的灵活性。
如果您还未阅读第 1 部分,您也许应该回头阅读一下那篇文章,以便了解更多有关这些数据绑定框架特性的知识。第 1 部分还讨论了数据绑定的代码生成和映射方法之间的权衡。在第 3 部分中,我将深入介绍新的 JiBX 框架。这包括 JiBX 如何将 Java 对象映射到 XML,以及 JiBX 为最小化运行时开销而在构建时使用的字节代码增强过程。请回来查看有关这个令人振奋的方法的完整信息,以提升框架性能!
您可以参阅本文在 developerWorks 全球站点上的英文原文.
请参与本文的论坛。(您也可以单击本文顶部或底部的 讨论来访问该论坛)。
如果要了解有关 XML 的背景知识,请尝试参加 developerWorks教程Introduction to XML(2002 年 8 月)。
阅读作者以前的文章“Data binding with Castor”,这篇文章讲述了 Castor 的映射数据绑定技术( developerWorks,2002 年 4 月)。
浏览作者以前的 developerWorks文章,性能(2001 年 9 月)和用法(2002 年 2 月),这两篇文章比较了几种 Java XML 文档模型。
阅读 Brett McLaughlin 的文章“Converting between Java objects and XML with Quick”,该文概述了 Quick,并向您展示了如何使用该框架来快速方便地将 Java 数据转换成 XML 文档,而不需要其它数据绑定框架所需的类生成语义( developerWorks,2002 年 8 月)。
有关对象关系数据绑定的基础知识简介,请阅读“Getting started with Castor JDO”一文(由 Bruce Snyder 撰写, developerWorks,2002 年 8 月)。
数据绑定框架
查找更多有关Java Architecture for XML Binding (JAXB)方面的内容,JAXB 是一个处于正在发展中的 Java 平台数据绑定标准。
更进一步了解Castor框架,它支持映射绑定和生成绑定。
了解JBind,它主要偏重于围绕 XML 构建应用程序代码框架,不太注重使 Java 语言应用程序方便地使用 XML。
Quick 框架是基于 Java 平台和 XML 出现之前的一系列开发成果。它为在 Java 平台上使用 XML 提供了极其灵活的框架。
研究Zeus的方方面面,它(类似于 Quick)根据 XML 文档的 DTD 描述来生成代码,使用 Zeus 要比使用 Quick 简单,但同时 Zeus 要更有限。
其它链接
阅读有关Java Technology and XML之间的相互影响。
参阅JSR 31 — XML 数据绑定规范(XML Data Binding Specification)。
在 developerWorksXML和Java 技术专区查找更多有关本文所涵盖的技术信息。
IBM WebSphere Studio提供了一套使 XML 开发自动化的工具(用 Java 和其它语言进行开发)。它与WebSphere Application Server紧密集成,但它也可以与其它 J2EE 服务器一起使用。
了解如何成为一名XML 及其相关技术的 IBM 认证开发人员。
关于作者
Dennis Sosnoski(dms@sosnoski.com )是西雅图地区的 Java 技术咨询公司Sosnoski Software Solutions, Inc. 的创始人和首席顾问,他是 J2EE、XML 和 Web 服务支持方面的专家。Dennis 有 30 多年的专业软件开发经验,最近几年致力于服务器端的 Java 技术。他经常在全国性的会议上就 Java 中的 XML 和 J2EE 技术发表言论,并主持Seattle Java-XML SIG。

_xyz