内容


XML 和 Java 技术:数据绑定第 4 部分

使用 JiBX

第 3 部分描述了 JiBX 的内部结构 — 现在将研究实际该如何使用 JiBX,以将 Java 对象灵活地绑定到 XML

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: XML 和 Java 技术:数据绑定第 4 部分

敬请期待该系列的后续内容。

此内容是该系列的一部分:XML 和 Java 技术:数据绑定第 4 部分

敬请期待该系列的后续内容。

本系列文章的 第 3 部分向您简介了 JiBX 数据绑定框架的体系结构。那篇文章简要概述了 JiBX 用于数据绑定的 以 Java 为中心的方法,并将其与其它大多数数据绑定框架使用的 以 XML 为中心的方法进行了比较。现在,在本文中(第 4 部分),您将了解如何在您的应用程序中使用这个用于数据绑定的以 Java 为中心方法的强大功能。

其它大多数针对 Java 语言的数据绑定框架都会强制您为文档提供 DTD 或 W3C XML Schema 语法,然后根据该语法生成类的集合。要使用上述框架,您需要使用这些生成的类,但是在大多数情况下,您只能稍微控制或根本无法控制这些类 — 它们基本上是 JavaBean 类型的包装器,具有简单的数据结构以及一些额外的框架代码。这些生成类的全部功能就是为使用 XML 文档中的数据提供一个接口。

由于使用了 get/set 方法来访问数据,因此 JavaBean 包装器方法有时候也以 面向对象的形式出现。实际上,它与您能够得到的面向对象开发还相去甚远,因为其数据对象缺乏可扩展性。真正的面向对象编程意味着对象隐藏其内部状态,并实现其使用该状态信息的行为。对于生成的代码方法而言,这通常是不可能的。

利用 JiBX,绑定到 XML 是作为应用于您的类的一个 方面进行的,而不是作为那些类的主要目的。因此,您使用了适合于您应用程序的面向对象的设计。它还让您可以自由地重构您的类,而无须更改绑定的 XML 文档结构。这种面向方面的方法甚至允许定义多个绑定以便由相同的类使用,这样就可以利用多个输入或输出 XML 文档结构,而无须更改您的代码。

XML 的束缚

JiBX 用于绑定的面向方面方法的核心是使用 绑定定义来控制如何在 Java 对象和 XML 文档之间进行相互转换。要了解这一过程的工作原理,请将 XML 文档考虑成树结构,其中元素的嵌套定义了树的分支。数据绑定使 XML 树和对象树(或者有时候是对象图,带有向上或跨整个树结构的链接)相互转换。JiBX 绑定定义是第三棵树,它表示 XML 树和对象树(在 JiBX 中,它们可以不同)结构的合并。这种合并的结构告诉 JiBX 如何在 XML 树和对象树之间相互转换。

图 1提供了一个有关如何在 JiBX 框架中使用绑定定义的简单示例。在本例中,XML 文档和绑定类使用了相同的结构 — customer元素拥有一个 name子元素,而 name元素拥有两个简单的文本值子元素。绑定定义简单地复制了该结构,从而在每个级别上提供了必要的信息,以将 XML 元素与相应的对象特性联系起来。

图 1. 绑定定义的角色
绑定定义的角色
绑定定义的角色

绑定组件

绑定定义中使用了几类元素。这些元素的作用及其可以包含的子元素的类型如表 1 所示。

表 1. 绑定定义中使用的元素

元素作用
binding

该元素是绑定定义的根元素,带有可选的用于绑定名和全局设置的属性。

子元素: namespaceformatmapping(至少必须有一个 mapping

namespace

该元素是名称空间声明,它定义了名称空间 URI 和相关联的前缀(带有用于数据编组的前缀)。

子元素:无

format

用于简单值和文本之间相互转换的格式定义。仅当您希望使用非标准的转换时,才需要该元素。

子元素:无

mapping

该元素定义了如何在特定类的对象和 XML 之间相互转换。每个 mapping 都是一个可重用的组件,需要在绑定定义中处理那种类型的对象的任何地方都可以引用该组件。作为 binding元素的子元素的映射定义称为 全局映射。

子元素: namespaceformatmapping、后跟 valuestructurecollection元素的任意组合

value

该元素为简单值(基本类型或带有提供格式的对象类型)提供了转换处理,以在简单值和文本之间进行转换。XML 表示可以是属性、简单文本,某些情况下也可以是纯文本或 CDATA 节点。

子元素:无

structure

该元素是绑定的结构组件,它可以表示 Java 对象和/或 XML 元素。通常,该结构表示一个 Java 对象,以及链接到该对象的 XML 元素。如果定义中缺少对象或 XML 元素时,则定义 结构映射

子元素: valuestructurecollection元素的任意组合

collection

该元素类似于 structure元素,但专门用于表示 Java 集合对象(集合对象是 JiBX Beta 测试版 2 中添加的)。

子元素: valuestructurecollection元素的任意组合

binding元素始终是绑定定义的根元素。它可以拥有的子元素有 namespaceformatmapping元素,这些元素必须按照这个顺序出现(前两个类型是可选的)。每个 mapping元素依次都可以在嵌套定义中包含上述这三个同类元素作为其子元素,后面可以跟着 valuestructurecollection元素的混合,后面这三个元素定义了 XML 和 Java 类之间关系的详细信息。

value元素表示 XML 文档的简单值组件,它可以是属性、简单的子元素(只带有文本内容)、文本或 CDATA。 structure元素更复杂。在最常见的情况下(如在 图 1的示例中), structure元素将带有复杂内容的子元素(在本示例中是 name 元素)与 Java 对象的对象-值特性( Customer 对象的 name 字段)相联系。但是该关系的两端都是可选的。这允许 structure元素定义一个没有相应对象的 XML 元素,或者定义一个没有相应元素的对象。我将在下面的示例中为您演示它的工作原理。

一个简单的绑定

在第 3 部分中,我介绍了 JiBX 为结构映射提供的灵活性的一些示例。我将在这里完整地研究实际的绑定定义。 图 2显示了第一个示例,是 XML 文档结构和 Java 对象结构之间的直接对应。该示例恰好是 图 1中使用的文档和类结构的完整版本。 清单 1为这种对应提供了完整的绑定定义。

图 2. 与 XML 的直接对应
与 XML 的直接对应
与 XML 的直接对应
清单 1. 直接对应的绑定定义
<binding>
  <mapping name="customer" class="Customer">
    <structure name="name" field="name">
      <value name="first-name" field="firstName"/>
      <value name="last-name" field="lastName"/>
    </structure>
    <value name="street1" field="street1"/>
    <value name="city" field="city"/>
    <value name="state" field="state"/>
    <value name="zip" field="zip"/>
    <value name="phone" field="phone"/>
  </mapping>
</binding>

使定义紧凑

清单 1提供了绑定定义的完整形式。但这并不是指定绑定的唯一途径。如果需要,JiBX(从 Beta 测试版 2 开始)将自动映射未指定的简单 Java 对象特性。被映射的特性可以采取字段的形式,或者采取 JavaBean 样式的 get/set 方法形式。利用这种缺省的映射允许使用更简短的 清单 2来替代清单 1 定义。

清单 2. 绑定定义的紧凑版本
<binding auto-link="fields">
  <mapping name="customer" class="Customer">
    <structure name="name" field="name"/>
  </mapping>
</binding>

这种紧凑的方法确实有一些缺陷。自动生成的特性绑定将始终遵循显式给定的任何定义,并且将按照定义的顺序出现。 图 1中的绑定情形恰恰是我所希望的 — name元素是 customer 元素的第一个子元素,并且 NameCustomer 类中的字段定义使用的顺序与对应的 XML 子元素的顺序相同。当 Java 数据与 XML 结构象在本例中这样紧密匹配时,自动生成绑定可以使绑定定义变得非常简单。

JiBX 确实提供了一些专门的选项来定制自动的特性绑定生成。这些选项允许您在生成对应的 XML 元素或属性名时省略字段或 JavaBean 特性名的前缀和后缀、控制 XML 名称的样式,以及设置自动生成中包含的字段或方法的访问级别。您甚至可以列出要在自动生成中明确包含或排除的字段或特性名。但是,对于本文中的其余示例,我仍将使用完整的绑定定义格式,以便于清晰地展示到底绑定了哪些值。

平铺树

简单的绑定示例实际上并不能充分地发挥 JiBX 的灵活性。在第 3 部分中,我还演示了两个结构映射的示例,它们处理 XML 文档和绑定 Java 类之间的结构差异。 图 3显示了该类型的第一个示例,它将相同的 XML 文档结构绑定到单个类,而不是以前使用的两个类。

图 3. 绑定到单个类
绑定到单个类
绑定到单个类

图 3的示例中,Java 类结构是 XML 文档的平铺版本。该结构不是为 XML name元素中的值使用单独类,而是将这些值直接包含在与父 customer元素对应的类中。 清单 3提供了该结构映射的完整绑定定义。

清单 3. 映射到单个类的结构

<binding>
  <mapping name="customer" class="Customer">
    
        <structure name="name">
      <value name="first-name" field="firstName"/>
      <value name="last-name" field="lastName"/>
    </structure>
    <value name="street1" field="street1"/>
    <value name="city" field="city"/>
    <value name="state" field="state"/>
    <value name="zip" field="zip"/>
    <value name="phone" field="phone"/>
  </mapping>
</binding>

如果您对 清单 1清单 3进行比较,您会发现这个平铺映射的绑定定义中的变化非常微小。与原始版本相比,只有一行绑定定义不同,新版本删除了 field 属性。 这告诉 JiBX:绑定定义中的 structure元素定义了 XML 中的一个元素(如由 name属性显示的),该元素映射到当前对象的一些特性。

整理树

图 4给出了结构映射的第二个示例。在本例中,Java 类结构使用了两个类,但是数据值的分解与 XML 文档的结构并不匹配 — XML 文档中 customer元素的数据值被拆分到两个类中,并且 name子元素的值被直接包含在与其父元素对应的类中。

图 4. 绑定到拆分的类
绑定到拆分的类
绑定到拆分的类

清单 4给出了 图 4绑定完整形式的绑定定义。清单 4 绑定定义与 清单 3绑定定义的唯一区别在于,我添加了一个 structure 元素,该元素对应于新的 Address 类。这个 structure元素包含一个 field属性,但是不包含 name属性,这告诉 JiBX: structure元素正在定义的这个对象特性在 XML 文档中无对应的元素。

清单 4. 到拆分类的结构映射
<binding>
  <mapping name="customer" class="Customer">
    <structure name="name">
      <value name="first-name" field="firstName"/>
      <value name="last-name" field="lastName"/>
    </structure>
    
        <structure field="address">
      <value name="street1" field="street1"/>
      <value name="city" field="city"/>
      <value name="state" field="state"/>
      <value name="zip" field="zip"/>
    
        </structure>
    <value name="phone" field="phone"/>
  </mapping>
</binding>

介绍一些其它选项

除了我已经介绍的那些选项之外,JiBX 绑定定义还提供了许多其它选项。其中一些选项在绑定定义元素的列表中略微提及过,例如使用 format元素进行定制序列化和反序列化,以及利用 namespace元素方便地处理名称空间。其它选项由绑定定义元素的属性控制。这些选项包括:可选值的缺省值、要在数据编组对象前或数据编出对象后调用的方法,以及用于引用对象的标识值。

JiBX 还包含一个定制数据编组和数据编出方法定义形式的通用扩展挂钩。这些选项和挂钩使您可以完全控制数据编组和数据编出过程,从而直接使用由 JiBX 上下文类定义的低级方法。我们并不打算将这类低级操作用于一般用途。但是,它确实为 JiBX 附件(add-on)提供了一些有趣的可能性。一种可能的用法就是允许在部分文档和文档模型(例如 DOM、JDOM 或 dom4j)之间相互映射。这便于处理一些特例,例如嵌入 XML 文档中的 XHTML 片段。

编译绑定

一旦您完成了绑定定义,就需要实际地将其编译成类文件。JiBX 提供了一个用于此目的的 绑定编译器。要使用它,您只需设置 Java 类路径,这样 JVM 就可以访问 JiBX 分发版中所包含的 jar 文件和您自己的类,然后运行将一个或多个绑定定义文件路径作为参数的 org.jibx.binding.Compile 程序。

绑定编译器将 JiBX 绑定代码添加到您的类文件,编译它们以在 JiBX 运行时使用。编译器执行该工作的方式是智能化的:如果多个绑定需要同一个添加代码,那么绑定编译器只要生成代码一次。同样,如果用修改过的绑定定义重新运行编译器,那么它将替代为旧绑定而添加的方法,而不只是添加新的方法。编译器甚至会除去以前为绑定添加的、现在不再使用的方法和类。最后一点,它只写到实际更改的类文件。这使得在更改(和编译)一些 Java 源代码文件后重新运行绑定编译器变得很安全,您无需重新编译 所有的Java 源文件。

运行绑定

一旦绑定编译器修改了 Java 类文件,那么您就可以准备使用 JiBX 运行时来对文档进行数据编组和数据编出。但是有一个小问题:只有在将 Java 源代码编译成类文件,并通过 JiBX 绑定编译器运行之后才能添加实际的绑定代码,因此您不能在您的源代码中直接访问该绑定代码。相反,您需要逐步完成一部分 JiBX 运行时,它会跟踪您正在使用的绑定定义,并在运行时使您连接到正确的代码。

这会使用 JiBX 运行时 jar 中包含的 org.jibx.runtime.BindingDirectory 类,以及 JiBX 在与您的代码相同的包中生成的一个类(如果您的代码在多个包中,则 JiBX 生成的类位于它所修改的第一个类文件的同一个包中)。但是,您不必费心地去了解这个生成类的详细信息;只需将由绑定中的全局映射(它是根元素 binding 的子元素)定义的某个类传递给 BindingDirectory 来访问它(如果您将多个绑定编译进了代码,那么还需要传递您希望使用的绑定名称)。代码很简单:

    IBindingFactory bfact = 
        BindingDirectory.getFactory(Customer.class);

在上述代码中, Customer 是绑定中全局映射的一个类名。返回的 org.jibx.runtime.IBindingFactory 接口提供了一些方法来构造数据编组和数据编出上下文,这些上下文又使您能执行实际的数据编组和数据编出操作。下面是一个数据编出的示例:

    IUnmarshallingContext uctx = bfact.createUnmarshallingContext();
    Object obj = uctx.unmarshalDocument
        (new FileInputStream("filename.xml"), null);

这只是几个数据编出调用变体中的一个 — 在本例中,用于对文件 filename.xml中的一个 XML 文档进行数据编出。可以传递一个阅读器而不是流来作为文档数据的源,还可以为文档指定编码(encoding) — 请参阅 JiBX 站点上的 JavaDocs 以获取详细信息。返回的对象是用绑定中全局映射定义的某个类的实例 — 您可以利用 instanceof检查类型,如果您知道它是什么类型,也可以直接将其强制转换成您的对象类型。

数据编组十分简单。下面是一个示例:

    IMarshallingContext mctx = bfact.createMarshallingContext();
    mctx.setIndent(4);
    mctx.marshalDocument(obj, "UTF-8", null,
        new FileOutputStream("filename.xml"));

如同上述数据编出示例一样,该示例只是数据编组调用可以使用的几个变体中的一种。它首先将输出 XML 的缩进设置为每个嵌套级别 4 个空格,然后将对象数据编组成 XML 文档,该文档将以 UTF-8字符编码(该编码是 XML 最常用的选择)写到文件 filename.xml。您可以传递一个记录器(而不是流)和其它一些变体 — 重申一次,请参阅 JiBX 站点上的 JavaDocs 以获取详细信息。要数据编组的对象必须是类的一个实例,该类是用绑定中的全局映射定义的。

未来方向

相对于其它可用于 Java 应用程序的 XML 数据绑定框架,JiBX 提供了很多优势。这些优势包括:非常快速的操作、紧凑的运行时分发以及 XML 文档格式和 Java 语言对象结构之间更好地进行了隔离。随着 JiBX 首个产品发行版发行日期的临近,对于众多的应用程序而言,似乎有了一个更好的选择。

JiBX 确实还存在着一些薄弱的领域。一个缺陷就是,目前它不支持根据 XML 语法生成代码。这可能是继我以前对以 XML 为中心的代码生成方法的缺陷做过一些评价之后的又一惊人之语,但是 JiBX 实际上提供了许多方法来避免其中很多的限制。如果可以使用 XML 语法来生成一组初始的类和相应的绑定定义,那么其优点就是可以快速地获取工作代码。同时,用户在修改绑定定义以使所有工作协调进行的同时,仍然拥有独立重构代码或语法的长期灵活性。

另一个有用的功能部件是一个依据 XML 语法验证绑定定义的工具。语法提供了大多数必要的信息,来说明某个绑定定义是否将真正正确地处理期望处理的文档。实际检查兼容性组合的工具将帮助防止在测试或部署过程中出现一些可能令人惊讶的情形。

当前将绑定框架方法添加到已编译类的字节码增强方法也是灵活性的用武之地。字节码增强的优点是可以使您的源代码保持整洁,但它的代价是在构建过程中添加一个步骤,并且在数据编组或数据编出的过程中跟踪被访问代码中的问题时,可能会产生混淆。最好为那些弊大于利的情形提供其它方法。

我认为对于该问题有一个相对简单的解决方案。将 JiBX 添加的代码反编译成源代码,并且将该代码合并到最初的 Java 语言源文件应该相当容易。一旦该工作完成,将自动对 JiBX 所需的方法进行内编译(compiled-in),只要用户不乱动 JiBX 添加的代码,就无需重新运行绑定编译器,除非绑定定义发生了变化。目前,我正在研究为 JiBX 添加对这类操作的支持,尽管在首个产品发行版发行后该工作才可能会完成。

有关其缺陷的最后一点说明是:与其它众多的数据绑定框架相比,JiBX 目前提供的验证支持是比较薄弱的。将来这种情形可能会发生变化。JiBX 确实支持要在数据编组对象前或数据编出对象后调用的那些方法,这些方法可用来处理大多数的验证形式。对于很多 XML 应用程序而言,相对于快速且方便地从 XML 文档访问数据这个主要目标,完全的验证支持是个次要目标,为了这个目标,JiBX 已经提供了另一种很好的方法。

结束语

在本文中,我为您演示了使用 JiBX 数据绑定框架的基础知识。我个人觉得 JiBX 提供了很多功能(这一点都不足为奇,因为我就是 JiBX 的开发者!)。对于那些需要将现有对象结构改写成 XML 的应用程序,以及希望分离实际 XML 结构和代码的任何应用程序而言,它特别有用。但 JiBX 肯定 不是Java 应用程序的所有 XML 需求的解决方案。

XML 用途繁多,用于在 Java 应用程序中使用 XML 的工具数量的不断增加表明了一个事实:不同的目的需要不同的工具。使用 XML 时最困难的部分通常就是了解哪个工具最适合于某个特定的应用程序。JiBX 旨在满足需要将 XML 文档解释为数据、并在内存中使用该数据的应用程序的需求,在这种应用程序中,侧重点是应用程序使用这些数据,而不是 XML 文档本身。

如果 JiBX 听起来能够满足您的需求,那么我建议您下载当前的分发版,并尝试一下。由于它是一个具有 BSD 样式许可证的开放源码项目,因此您可以自由地修改代码,以满足您的需求 — 您甚至无需公布您的修改。我自然希望有很多人觉得它有用,并希望他们 确实为该工具的扩展做了贡献,同时添加一些工具以有助于该项目将来的发展。

在我的下一篇文章中,我将研究一下最近发布的 JAXB 数据绑定标准和参考实现,从而结束有关数据绑定的本系列文章。下一篇文章将对 JAXB 提供的一些定制选项进行实验。JAXB 最擅长的领域恰好是 JiBX 最薄弱的领域,因此,对于这个涵盖了数据绑定整个范围工具和技术的专题系列,最后一篇文章将是一个很好的总结。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=21053
ArticleTitle=XML 和 Java 技术:数据绑定第 4 部分: 使用 JiBX
publish-date=04012003