JiBX 1.2,第 2 部分: 从 XML 模式到 Java 代码,通过 XML 模式生成更干净的自定义 Java 代码

安装 JiBX

在继续学习本教程之前,您需要先安装 JiBX。下载 最新的 1.2.x 发行版 ZIP 并将其解压缩到系统中便于访问的位置。您将得到名为 jibx 的目录,其中包含所有的 JiBX JAR、文档、示例,甚至源代码。

安装教程代码

现在下载教程 样例代码,也是以 ZIP 文件的形式提供的。在系统中安装样例代码的最简单方法是在 JiBX 安装的根目录中解压缩 ZIP(或者对于 Windows®,将 dwcode2 目录从 ZIP 文件中复制到 JiBX 安装的根目录中)。此操作应当在 jibx 目录中创建 dwcode2 子目录,而且 dwcode2 子目录中包含示例文件(包括 build.xml、custom.xml 和其他文件)。

样例代码包括自动运行 JiBX 工具并处理示例涉及的其他步骤的 Ant 构建文件。如果直接将样例代码安装到 JiBX 安装目录中,则构建可以访问 JiBX JAR 而无需任何附加配置。如果在其他位置安装样例代码,则仍然可以使用 Ant 构建。在这种情况下,您只需编辑样例代码目录中的 build.properties 文件,然后将 jibx-home 属性的值改为 JiBX 安装路径。

通过模式生成默认绑定和代码

通过 XML 模式定义生成 JiBX 绑定定义及相应的 Java 代码十分简单。您将在本节中了解具体操作。

简单示例模式简介

我将使用 第 1 部分 中生成的一个模式作为一个简单示例。清单 1 显示了该模式的简短版本,用于表示在线商店的订单。样例代码的 dwcode2 目录中的 starter.xsd 提供了完整的模式。


清单 1. 第一个示例模式
Supported shipment methods. The "INTERNATIONAL" shipment methods can only be used for orders with shipping addresses outside the U.S., and one of these methods is required in this case. ... Order line item information. Stock identifier. This is expected to be 12 characters in length, with two leading alpha characters followed by ten decimal digits. Number of units ordered. Price per unit. Address information. First line of street information (required). ... State abbreviation (required for the U.S. and Canada, optional otherwise). Postal code (required for the U.S. and Canada, optional otherwise). Customer information. ... Order information. Billing address information. ... Date order was placed with server. ...

生成默认绑定和代码

要通过 XML 模式生成 JiBX 绑定和 Java 类,您只需运行 JiBX 发行版中的 jibx-tools.jar 附带的 org.jibx.schema.codegen.CodeGen 工具。您可以通过命令行直接运行该工具,也可以通过 Apache Ant 之类的构建工具间接运行该工具。

本教程的下载部分包括 Ant build.xml 脚本,其中 codegen 目标用于运行生成。

要尝试使用此脚本,请打开已安装下载的 dwcode2 目录中的控制台,然后输入 ant codegen。如果系统中安装了 Ant 并且根据说明安装了下载代码,则应当会看到类似图 1 所示的输出:


图 1. 使用 Ant 构建
JiBX 1.2,第 2 部分: 从 XML 模式到 Java 代码,通过 XML 模式生成更干净的自定义 Java 代码_第1张图片

您还可以直接从控制台运行 CodeGen。为此,您需要:

  1. 在 Java 类路径中包括 jibx-tools.jar。
  2. 指定 org.jibx.schema.codegen.CodeGen 为要运行的类。
  3. 列出要生成的模式定义。

提供的 Ant codegen 目标将使用几个附加参数告诉 CodeGen 使用 gen/src 目录作为生成的数据模型包结构的根目录,并且在运行生成前删除该目录中的所有现有文件。下面是用于从 dwcode2 目录的控制台中复制 Ant codegen 目标的 Java 命令行(假定您遵循了推荐安装说明):

java -cp ../lib/jibx-tools.jar org.jibx.schema.codegen.CodeGen -t gen/src -w starter.xsd

对于 Windows,该命令为:

java -cp ../lib/jibx-tools.jar org.jibx.schema.codegen.CodeGen -t gen/src -w starter.xsd

您可以通过命令行向 CodeGen 传递许多其他选项。您稍后将在教程中看到这些选项。现在,让我们看一看生成的 Java 代码。

生成的工件

生成的代码由五个类组成,分别对应于 清单 1 模式中的五个全局类型定义。清单 2 显示了生成的代码的一些样例,其中包括 org.jibx.starter.Order 类的摘录及整个 org.jibx.starter.Shipping 类:


清单 2. 生成的代码

/** * Order information. * * Schema fragment(s) for this class: *

 *  *  *  *  *  *  *  *  *  *  *  *  *  * 
*/ public class Order { private long orderNumber; private Customer customer; private Address billTo; private Shipping shipping; private Address shipTo; private List itemList = new ArrayList(); private Date orderDate; private Date shipDate; private Float total; ... /** * Get the 'shipTo' element value. Shipping address information. If missing, the * billing address is also used as the shipping address. */ public Address getShipTo() { return shipTo; } /** * Set the 'shipTo' element value. Shipping address information. If missing, the * billing address is also used as the shipping address. */ public void setShipTo(Address shipTo) { this.shipTo = shipTo; } /** * Get the list of 'item' element items. */ public List getItems() { return itemList; } /** * Set the list of 'item' element items. */ public void setItems(List list) { itemList = list; } ... } /** * Supported shipment methods. The "INTERNATIONAL" shipment methods can only be used for orders with shipping addresses outside the U.S., and one of these methods is required in this case. * * Schema fragment(s) for this class: *
 *  *  *  *  *  *  *  *  *  * 
*/ public enum Shipping { STANDARD_MAIL, PRIORITY_MAIL, INTERNATIONAL_MAIL, DOMESTIC_EXPRESS, INTERNATIONAL_EXPRESS }

通过 清单 2 可以看到,CodeGen 在生成的代码中将模式文档自动转换为 Javadoc(在此处显示为每个类 Javadoc 中的前导注释,以及显示为 getShipTo()setShipTo() 方法的注释部分)。默认情况下,CodeGen 还合并类 Javadoc 中的实际模式定义,而对于 get/set 属性访问方法,它将描述与属性对应的模式组件。

对于重复值,例如清单 1 订单 complexType 定义中的重复项目元素,CodeGen 将默认生成 Java 5 类型的列表。对于 simpleType 限制枚举,例如清单 1 中的送货类型,CodeGen 将默认生成 Java 5 枚举类型。清单 2 中显示了为这两个实例生成的代码。

生成的 JiBX 绑定

除了生成的代码之外,CodeGen 还将产生 JiBX 绑定定义(在本例中为 binding.xml 文件),该绑定定义将告诉 JiBX 绑定编译器如何在 Java 类与 XML 之间进行转换。绑定定义包含 JiBX 所完成的转换的完整信息,因此它们必然非常复杂。幸运的是,使用 CodeGen 绑定和代码生成,您无需了解绑定定义即可使用 JiBX,因此本教程不会介绍这部分的详细信息。

处理 XML 文档

在本节中,您将了解如何在运行时运行 JiBX 绑定编译器和使用 JiBX,从而处理 XML 文档。

运行 JiBX 绑定编译器

要在处理 XML 文档时使用生成的绑定定义,首先需要运行 JiBX 绑定编译器。按照绑定定义的指定,绑定编译器将把字节码添加到编译后的类文件,这些文件实际实现了与 XML 之间的来回转换。每次重新编译 Java 类或修改绑定定义时,都必须运行绑定编译器,因此一般最好把绑定编译器步骤添加到项目的标准构建流程中。

jibx-bind.jar 中的 JiBX 发行版附带了绑定编译器。JiBX 文档将提供通过各种方法运行绑定编译器的完整信息,包括如何在运行应用程序时(而非在构建时)调用绑定编译器。JiBX 还提供了 Eclipse 和 IntelliJ IDEA 的插件,这样在使用这些 IDE 时将自动运行绑定编译器。

根据本教程的目的,您将把一切简单化并且只通过 Ant 运行绑定编译器。build.xml 的 compile 目标将通过编译生成的代码和提供的测试程序来为绑定做准备,而 bind 目标实际运行绑定编译器。假定您已经运行了 codegen 目标,图 2 将显示运行这些目标时应当会看到的输出(您还可以通过在命令行中按顺序列出这些目标来运行全部三个目标:ant codegen compile bind)。


图 2. Ant 构建 compilebind 任务
JiBX 1.2,第 2 部分: 从 XML 模式到 Java 代码,通过 XML 模式生成更干净的自定义 Java 代码_第2张图片

在运行时使用 JiBX

清单 3 显示了匹配模式的简单测试文档,包含在教程的代码下载中,名为 starter.xml:


清单 3. 订单模式的测试文档

12345678 5678 John Smith 12345 Happy Lane Plunk USA PRIORITY_MAIL 333 River Avenue Kirkland

下载包还包括一个简单测试程序,它在本文中显示为清单 4,用于演示如何使用 JiBX 解组编组 文档。编组是在内存中生成对象的 XML 表示的过程,可能包括从初始对象链接的对象;解组是编组的反向过程,它将通过 XML 表示在内存中构建一个对象(还有可能是一些链接的对象)。Ant run 目标将执行此测试程序,使用 清单 3 文档作为输入并把编组后的文档副本写到名为 out.xml 的文件中。


清单 4. 测试程序

public class Test { /** * Unmarshal the sample document from a file, compute and set order total, then * marshal it back out to another file. * * @param args */ public static void main(String[] args) { if (args.length < 2) { System.out.println("Usage: java -cp ... " + "org.jibx.starter.Test in-file out-file"); System.exit(0); } try { // unmarshal customer information from file IBindingFactory bfact = BindingDirectory.getFactory(Order.class); IUnmarshallingContext uctx = bfact.createUnmarshallingContext(); FileInputStream in = new FileInputStream(args[0]); Order order = (Order)uctx.unmarshalDocument(in, null); // compute the total amount of the order float total = 0.0f; for (Iterator iter = order.getItems().iterator(); iter.hasNext();) { Item item = iter.next(); total += item.getPrice() * item.getQuantity(); } order.setTotal(new Float(total)); // marshal object back out to file (with nice indentation, as UTF-8) IMarshallingContext mctx = bfact.createMarshallingContext(); mctx.setIndent(2); FileOutputStream out = new FileOutputStream(args[1]); mctx.setOutput(out, null); mctx.marshalDocument(order); System.out.println("Processed order with " + order.getItems().size() + " items and total value " + total); } catch (FileNotFoundException e) { e.printStackTrace(); System.exit(1); } catch (JiBXException e) { e.printStackTrace(); System.exit(1); } } }

图 3 显示了运行 run 目标时应当会看到的输出:


图 3. Ant 构建 run 任务
JiBX 1.2,第 2 部分: 从 XML 模式到 Java 代码,通过 XML 模式生成更干净的自定义 Java 代码_第3张图片

这是 第 1 部分 中使用的同一个测试程序,并且同样具有第一部分教程中讨论的限制。就像在第 1 部分中一样,out.xml 文件包含了将解组原始文档获得的订单数据重新编组后生成的输出。

CodeGen 自定义简介

在本节中,您将了解自定义 CodeGen 以控制通过简单模式生成的代码结构的基础知识。

简单自定义示例

CodeGen 支持在代码和绑定生成的许多方面进行丰富的自定义。要应用的自定义集将作为 XML 文档传递给 CodeGen,其中包括与模式或模式组件相关的嵌套元素。清单 5 给出了一个简单示例:


清单 5. 简单自定义示例

					

清单 5 自定义包含单个无名称空间的模式元素,以及针对特定自定义的几个不同属性(到目前为止,您只是在使用单个模式定义,因此可以使用这种非常简单的自定义形式。稍后在本教程中,您将看到使用多个模式的自定义示例)。第一个自定义属性 — prefer-inline="true" — 将告诉 CodeGen 内联只引用一次的模式定义,而不是执行将其保留为单独类的默认行为。第二个属性 — show-schema="false" — 将阻止在类 Javadoc 中嵌入模式定义。enumeration-type="simple" 将生成简单的类型安全枚举而非 Java 5 枚举。

最后一对属性将一起工作。默认情况下,CodeGen 将为指定为输入的模式中的每个全局类型定义都生成一个单独的顶级类,并且在为每个 complexType 生成的 JiBX 绑定中生成对应的抽象映射。generate-all="false" 属性将更改这种默认行为,只显式地生成特别包括的或引用自 included 类型的那些 complexType。随后,includes="order item" 属性将给出要生成的名称,选择这些名称以保持与测试程序的兼容性 — 需要 元素,原因是它是实例文档的根元素,并且需要 complexType 项,因为测试程序期望在计算订单总数时找到此类型的独立顶级类。

通过使用 Ant custgen 任务而非 codegen 任务(或者只使用 full 任务,该任务将运行完整的 clean custgen compile bind run 目标序列),您可以尝试这些自定义。清单 6 显示了生成的代码片段,您可以将其与 清单 2 所示的默认生成的代码相比较。除了简化的类 Javadoc 之外,最大的差别是 Customer 类现在是内联的,而 Shipping 类现在是使用自定义的类型安全枚举类的内部类。


清单 6. 用自定义生成的代码
/** * Order information. */ public class Order { private long orderNumber; private long customerCustomerNumber; private String customerFirstName; private String customerLastName; private Address billTo; private Shipping shipping; private Address shipTo; private List itemList = new ArrayList(); private Date orderDate; private Date shipDate; private Float total; ... /** * Supported shipment methods. The "INTERNATIONAL" shipment methods can only be used for orders with shipping addresses outside the U.S., and one of these methods is required in this case. */ public static class Shipping { private final String value; public static final Shipping STANDARD_MAIL = new Shipping( "STANDARD_MAIL"); public static final Shipping PRIORITY_MAIL = new Shipping( "PRIORITY_MAIL"); public static final Shipping INTERNATIONAL_MAIL = new Shipping( "INTERNATIONAL_MAIL"); public static final Shipping DOMESTIC_EXPRESS = new Shipping( "DOMESTIC_EXPRESS"); public static final Shipping INTERNATIONAL_EXPRESS = new Shipping( "INTERNATIONAL_EXPRESS"); private static final String[] values = new String[]{"DOMESTIC_EXPRESS", "INTERNATIONAL_EXPRESS", "INTERNATIONAL_MAIL", "PRIORITY_MAIL", "STANDARD_MAIL"}; private static final Shipping[] instances = new Shipping[]{ DOMESTIC_EXPRESS, INTERNATIONAL_EXPRESS, INTERNATIONAL_MAIL, PRIORITY_MAIL, STANDARD_MAIL}; private Shipping(String value) { this.value = value; } public String toString() { return value; } public static Shipping convert(String value) { int index = java.util.Arrays.binarySearch(values, value); if (index >= 0) { return instances[index]; } else { return null; } } public static Shipping fromValue(String text) { Shipping value = convert(text); if (value == null) { throw new IllegalArgumentException("Value /'" + text + "/' is not allowed"); } else { return value; } } } }

还有许多其他的自定义也可用于 CodeGen。您稍后将在本教程中看到这些自定义的一些示例,但是为了更好地说明这些自定义的强大之处,有必要继续介绍一个更复杂的模式。

尝试一个实际的模式

独立的模式定义非常适合用于简单演示,但是在应用到企业应用程序中广泛使用的复杂模式定义时,它无法让用户了解工具的工作原理。现在将以一个符合行业标准的 HR-XML 模式定义为例,继续介绍一个更实际的示例。

HR-XML TimeCard 模式

HR-XML Consortium 是为开发针对人力资源的 XML 表示的开放标准而成立的组织。它代表着 110 多家企业成员,并且几乎有 50 家技术公司通过了符合其标准的认证。

本教程中使用的 HR-XML 模式包含 157 个模式,其中混合了顶级文档定义和常用组件。CodeGen 可以轻松地处理这些模式,但是生成的类数目和相互关系的复杂度掩盖了模式处理的更有趣方面。为了关注这些细节,这里使用的 HR-XML 的子集包括 TimeCard 元素的一个顶级文档定义,以及作为 TimeCard 定义的一部分引用的常用组件 — 总计七个模式定义。

您可以在 hrxml/schemas 目录下找到本教程中使用的 HR-XML 模式定义子集。清单 7 显示了经过编辑的 TimeCard 元素定义的主要模式。这将给出一个 HR-XML 模式样式样例,该样例将同时使用嵌套及全局类型的定义,并且比第一个示例包含更广泛的模式结构,包括:

  • 组合器(compositor)(如 TimeCardType 定义中的某些嵌入式 complexType 所示)
  • 粒子(particle)(查看清单开始部分的 AdditionalDataType 定义)
  • (查看清单末尾的 TimeCardDuration 定义)
  • 非枚举型 限制


清单 7. HR-XML TimeCard 模式
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...

TimeCard 生成的代码

hrxml 目录中的 Ant build.xml 文件将定义尝试为 TimeCard 模式生成基本代码的 Ant 目标,包括默认生成和几个自定义示例(稍后讨论)。样例目录还包含一个测试程序 org.jibx.hrxml.Test。它将使用生成的数据模型类将样例文档解组,然后将文档重新编组并将结果与原始文档相比较。并且样例目录中有一组来自 HR-XML 发行版的测试文档。codegen 目标将使用默认值运行 CodeGen,compile 将编译生成的代码和测试代码,bind 将编译 JiBX 绑定,而 roundtrip 将对样例文档运行测试程序。您还可以使用 full 任务按顺序运行所有这些步骤。

大多数通过模式生成代码的方式都将为每个 complexType 定义及枚举 simpleType 生成一个单独的类。通过在可能的位置检查引用和内联定义,并且忽略包括和导入的模式定义中未使用的定义,CodeGen 通常能够减少生成的类的数量。在 TimeCard 模式中,有总计 10 个全局(命名的)complexType 和附加的 23 个本地(匿名)complexType,以及 8 个枚举 simpleType。生成的默认数据模型将包含 15 个顶级类和 23 个内部类,要比根据模式组件计算的少一些。您稍后将看到,如果不需要用到全部模式组件,如何使用自定义进一步简化数据模型。

处理

清单 8 显示了 CodeGen 如何处理 TimeCardType complexType 定义中的两个元素之间的选择。默认情况下,CodeGen 将使用一个选择变量来跟踪目前处于活动状态的选择。选择中包括的值的 set 方法将允许您写入目前选择的新值,但是不能直接更改选择(如果您尝试这样做,则抛出 IllegalStateException)。要在设定后更改目前的选择,首先需要调用一个清除方法(此处为 clearReportedResourceSelect()),该方法将重置选择状态。


清单 8. HR-XML TimeCard 生成的代码样例

/** * Schema fragment(s) for this class: *

 *  *  *  *  *  *  *  *  *  *  *  *  *  * ... */ public class TimeCardType { private EntityIdType id; private int reportedResourceSelect = -1; private final int REPORTED_RESOURCE_PERSON_CHOICE = 0; private final int RESOURCE_CHOICE = 1; private TimeCardPersonType reportedResourcePerson; private Resource resource; ... private void setReportedResourceSelect(int choice) { if (reportedResourceSelect == -1) { reportedResourceSelect = choice; } else if (reportedResourceSelect != choice) { throw new IllegalStateException( "Need to call clearReportedResourceSelect() before changing existing choice"); } } /** * Clear the choice selection. */ public void clearReportedResourceSelect() { reportedResourceSelect = -1; } /** * Check if ReportedResourcePerson is current selection for choice. * * @return true if selection, false if not */ public boolean ifReportedResourcePerson() { return reportedResourceSelect == REPORTED_RESOURCE_PERSON_CHOICE; } /** * Get the 'Person' element value. * * @return value */ public TimeCardPersonType getReportedResourcePerson() { return reportedResourcePerson; } /** * Set the 'Person' element value. * * @param reportedResourcePerson */ public void setReportedResourcePerson( TimeCardPersonType reportedResourcePerson) { setReportedResourceSelect(REPORTED_RESOURCE_PERSON_CHOICE); this.reportedResourcePerson = reportedResourcePerson; } /** * Check if Resource is current selection for choice. * * @return true if selection, false if not */ public boolean ifResource() { return reportedResourceSelect == RESOURCE_CHOICE; } /** * Get the 'Resource' element value. * * @return value */ public Resource getResource() { return resource; } /** * Set the 'Resource' element value. * * @param resource */ public void setResource(Resource resource) { setReportedResourceSelect(RESOURCE_CHOICE); this.resource = resource; }

对于大多数应用程序来说,这类选择处理工作得非常好,可以防止用户尝试在一个选择中设置多个备选值。不过,可以使用自定义修改默认选择处理,因此如果您不喜欢这种选择处理形式,您可以轻松地更改它。choice-check 属性将控制如何在生成的代码中检查 > 的选择状态。choice-check="disable" 值将禁用所有检查并且不跟踪选择状态,让用户设置一个选择状态并且每个选择只有一个值。choice-check="checkset"清单 8 中所示的默认处理相符,其中只有 set 方法将检查目前的设置并抛出异常。choice-check="checkboth" 还将在调用 get 方法时检查选择状态,如果 get 方法与目前的选择状态不符,则抛出异常。最后,choice-check="override" 把默认处理修改为在设置选择的任何值时始终更改目前的状态,而不是在之前设置其他状态时抛出异常。

choice-exposed 自定义属性将与 choice-check 设置结合使用,这些设置将跟踪目前的选择状态。choice-exposed="false" 值将选择状态常量、状态变量值和状态更改方法全部设置为私有,匹配 清单 8 中所示的默认代码生成。choice-exposed="true" 将为状态变量添加 get 方法,使得所有这些内容可以公开访问。这将允许您轻松地使用 Java switch 语句以根据目前的状态执行不同的代码,而不再需要使用多条 if 语句。

这两个属性可以在任意级别的自定义中使用,允许您为最外层自定义中所有生成的代码轻松地设置行为,同时仍然保留根据具体情况执行其他操作的能力。

mixed="true" 处理

和许多企业模式一样,HR-XML 模式将使用 模式组件为数据创建扩展点,这些扩展点可以独立于原始模式,由用户定义。默认情况下,CodeGen 将使用 org.w3c.dom.Element 对象(如果 中的 maxOccurs 值大于 1,则使用 Element 列表)处理 模式组件。Element 对象可用于表示任意一个 XML 元素(包括所有属性、名称空间声明和内容),因此它将提供处理任何匹配模式定义的文档所需的所有灵活性。

清单 9 显示了匹配 清单 7 模式样例的 组件的生成代码。由于 使用 maxOccurs="unbounded",因此生成的代码将使用一个 Element 列表。


清单 9. 生成的代码样例

/** * ... * Schema fragment(s) for this class: *

 *  *  *  *  *  *  * 
*/ public class AdditionalDataType { private List anyList = new ArrayList(); private String type; /** * Get the list of sequence items. * * @return list */ public List getAny() { return anyList; } /** * Set the list of sequence items. * * @param list */ public void setAny(List list) { anyList = list; } ... }

清单 9 中模式定义的某些方面是被忽略的,或者只是由 CodeGen 处理了一部分。首先,封装的 > 定义指定了 mixed="true",这意味着允许将字符数据与 粒子所表示的元素相混合。CodeGen 所生成的数据模型没有空间来保存这类字符-数据内容,因此在文档被解组时,这些内容将被丢弃。其次, 将使用 processContents="strict",意味着实例文档中存在的所有元素都需要拥有自己的模式定义。CodeGen 将忽略此属性,尽管可能使用其他形式的 处理(下面将讨论)得到类似的效果。CodeGen 还将忽略 名称空间限制。清单 9 使用 namespace="##any",表示匹配 的元素都不受名称空间的限制,但是举例来说,如果该值是 namespace="##other",则结果应当相同。

您可以在任意级别的自定义中使用 any-handling 自定义属性,选择处理 的其他方式。值 any-handling="discard" 只忽略生成的数据模型中的 ,并在出现解组时丢弃与 对应的所有元素。any-handling="dom" 将匹配默认处理,使用 org.w3c.dom.Element 表示匹配 的元素。最后,any-handling="mapped" 将生成代码,要求每个匹配 的元素都有一个全局模式定义(大致对应于 processContents="strict" 模式条件)。在这最后一种情况中,数据模型将使用 java.lang.Object 表示元素,并且对象的实际运行时类型匹配全局模式定义。

处理

和大多数通过模式生成代码的方式一样,CodeGen 将忽略或者只部分处理 定义的许多方面。 限制就是这类有限支持的一个例子。在模式所定义的各种 simpleType 限制(包括长度限制、值范围,甚至正则表达式模式)中,只有 > 限制目前是在生成的数据模型中强制执行的。

CodeGen 目前也忽略 。清单 10 显示了匹配 引用的生成的代码,以及匹配代码的初始模式片段(位于清单底部)。您可以在清单 10 中看到,每个指向联合(union)类型(包括清单中所示的 TimeCardDuration 类型和 AnyDateTimeType)的引用在生成的代码中是用简单的 String 值表示的。


清单 10. 生成的代码样例及原始模式

/** * Schema fragment(s) for this class: *

 *  *  *  *  *  *  *  *  *  *  *  *  * ... * 
*/ public static class TimeInterval { private EntityIdType id; private String startDateTime; private int choiceSelect = -1; private final int END_DATE_TIME_CHOICE = 0; private final int DURATION_CHOICE = 1; private String endDateTime; private String duration; private String duration1; ... ... ...

模式修改

如果将 清单 10 顶部的 Javadoc 中嵌入的模式片段与清单底部的实际模式片段相比较,您将看到初始模式中的 union simpleType 引用在 Javadoc 版本中已经替换为 xs:string 引用。这是故意的,并且它在 CodeGen 所执行的若干种模式结构转换中具有代表性。诸如删除 simpleTypesimpleType 限制(而非 )之类的转换都是被硬编码到 CodeGen 操作中的。其他转换都受自定义控制。不管使用哪种方法,Javadocs 中包括的模式片段总是显示转换后的模式,因为实际上用于生成代码的就是转换后的模式。

您将在教程的后几节中看到受自定义控制的更多类型的转换。

自定义 数据模型

本教程中先前的 示例 显示了一些简单的 CodeGen 自定义。现在,您已经了解了 CodeGen 如何处理带有默认设置的 HR-XML TimeCard 模式,我们接下来将研究一些更强大的自定义。

自定义数据模型

CodeGen 使用默认设置生成的数据模型代码有一些弱点。首先,模式类型名称全都以 Type 为结尾,并且这种情况延续到对应的生成的类名上,导致名称过长。通过模式名称空间生成的包名 org.hrxml.ns 是合理的,但是如果包名可以表明该数据模型专门用于 TimeCard 文档,那么效果会更好。

清单 11 显示了生成的数据模型类的另外一个缺点,其中 java.math.BigInteger 用于表示 xs:integer 类型。这使用标准 Java 类时 xs:integer 的最精确表示,但是与简单的 int 原语或 java.lang.Integer 对象类型相比,BigInteger 并不好用。糟糕的是,即使使用 xs:int 会更恰当,人们也通常使用 xs:integer 类型编写模式,因此开发人员可能会在生成的代码中遇到 BigInteger 值。本例就是这种情况:GenderCode 允许的实际值全都是个位数(如清单底部的原始模式片段所示)。


清单 11. xs:integer 生成示例

/** * Schema fragment(s) for this class: *

 *  * 
*/ public class GenderCode { private BigInteger genderCode; /** * Get the 'GenderCode' element value. * * @return value */ public BigInteger getGenderCode() { return genderCode; } /** * Set the 'GenderCode' element value. * * @param genderCode */ public void setGenderCode(BigInteger genderCode) { this.genderCode = genderCode; } } Must conform to ISO 5218 - Representation of Human Sexes (0 - Not Known; 1 - Male; 2 - Female; 9 - Not specified)

清单 12 显示了可以改进生成数据模型的这些缺点的自定义。package="org.hrxml.timecard" 属性将提供用于生成的类的 Java 包。type-substitutions="xs:integer xs:int" 属性将定义 CodeGen 所应用的模式类型置换,在本例中使用 xs:int 类型替换模式中引用的 xs:integer。通过向列表中添加更多类型名称,使用空格分隔每对置换以及其中的类名,您可以定义多对置换。

嵌套的 name-converter 元素将确定如何处理被转换为 Java 名称的 XML 名称。在本例中,strip-suffixes="Type" 属性将告诉 CodeGen 只要 Type 出现在名称末尾就删除它。您可以用一张用空格分隔的列表来提供要通过此属性删除的多个备选内容。您也可以使用 strip-prefixes 属性删除名称中不必要的前导文本,以及其他几种形式的自定义。如果您需要在名称转换中执行一些特殊操作,甚至可以用您自己的实现来替换默认的名称转换类。有关这些 name-converter 选项的完整信息,请参阅 JiBX CodeGen 文档。

最后,嵌套的 class-decorator 元素将向代码生成序列中添加一个修饰符(decorator)。在本例中,修饰符是 CodeGen 发行版中提供的预定义内容,它将添加用于集合值的有用的支持方法。在 CodeGen 构造数据模型类的源代码时,它将按顺序调用所有已配置的代码生成修饰符,并且这些修饰符可以进行修改或者添加到 CodeGen 生成的字段、方法和类构造中。使用 Eclipse AST 实现,把所有这些构造作为抽象语法树(Abstract Syntax Tree,AST)组件传递给修饰符。提供的修饰符(包括在这里用于添加方法的 org.jibx.schema.codegen.extend.CollectionMethodsDecorator 修饰符,以及用于向数据模型类中添加 java.io.Serializable 接口和可选的版本 id 的 org.jibx.schema.codegen.extend.SerializableDecorator)演示了如何结合使用 Eclipse AST 以扩展 CodeGen,因此这些类的源代码是编写您自己的修饰符的最佳起点。


清单 12. TimeCard 自定义示例

					

  
  

您可以使用 custgen1 Ant 目标尝试执行清单 12 中的自定义,也可以使用 custom1 目标运行完整的生成、编译、绑定及测试操作。清单 13 显示了应用自定义的结果。TimeCardType 类名已经改为 TimeCard,并且除了 List get 和 set 方法之外,现在还添加了 size、add、indexed get 和 clear 方法。在 GenderCode 类中,BigInteger 引用已经被替换为一个简单的 int 原语类型。


清单 13. 自定义的数据模型

/** * Schema fragment(s) for this class: *

 * ... * 
*/ public class TimeCard { ... private List reportedTimeList = new ArrayList(); ... /** * Get the list of 'ReportedTime' element items. * * @return list */ public List getReportedTimes() { return reportedTimeList; } /** * Set the list of 'ReportedTime' element items. * * @param list */ public void setReportedTimes(List list) { reportedTimeList = list; } /** * Get the number of 'ReportedTime' element items. * @return count */ public int sizeReportedTime() { return reportedTimeList.size(); } /** * Add a 'ReportedTime' element item. * @param item */ public void addReportedTime(ReportedTime item) { reportedTimeList.add(item); } /** * Get 'ReportedTime' element item by position. * @return item * @param index */ public ReportedTime getReportedTime(int index) { return reportedTimeList.get(index); } /** * Remove all 'ReportedTime' element items. */ public void clearReportedTime() { reportedTimeList.clear(); } ... } /** * Schema fragment(s) for this class: *
 *  * 
*/ public class GenderCode { private int genderCode; /** * Get the 'GenderCode' element value. * * @return value */ public int getGenderCode() { return genderCode; } /** * Set the 'GenderCode' element value. * * @param genderCode */ public void setGenderCode(int genderCode) { this.genderCode = genderCode; } }

清除不使用的定义

在使用初始简单模式的第一个自定义示例中,您看到了通过使用 generate-all="false" 禁止生成每个全局定义,并使用 includes 列表强制生成特定定义,从而控制生成的数据模型中包括的类型定义。清单 14 显示了添加了这些属性的 TimeCard 模式的修改后的自定义,只包含要包括到生成的数据模型中的 TimeCard 元素(当然,还包含 TimeCard 表示所使用的一切内容)。


清单 14. 只包含 TimeCard 组件的自定义

					

  
  
  

您可以使用 custgen2 Ant 目标尝试用 CodeGen 使用此自定义,或者使用 custom2 目标运行完整的生成、编译、绑定及测试。此更改将把数据模型中顶级类的数目从 15 个减少到 10 个 — 这是简化数据模型的好开端。

自定义独立组件

到目前为止,您只看到了在整套模式中或者独立模式中应用的自定义示例。您还可以自定义 CodeGen,使其处理模式定义 的特定组件,包括全局定义及嵌入到全局定义中的内容项。可用的自定义包括从数据模型中清除组件、更改组件使用的类或值的名称,以及更改组件的模式类型。

如果要控制模式,则从数据模型中清除组件的自定义不是特别有用 — 在那种情况下,直接更改模式始终更简单些。但是企业数据交换模式通常包括专用组件,这些专用组件可能不适合用于使用这些模式的特定应用程序,并且这些模式通常不在您的控制范围内。在这种情况下,使用自定义将允许您简化数据模型,而无需触及提供的模式。

组件自定义

模式组件的自定义方式是,把自定义元素与表示组件的模式定义元素关联在一起。您可以使用多种不同的方法建立自定义与模式元素之间的关联,因为在特定情况下,一种方法可能比另一种方法更方便。不过,关联有一部分是固定的:自定义元素的名称必须始终与模式组件元素名称相符。因此要自定义模式中的 定义,您需要使用 自定义元素(没有名称空间)。

清单 15 将显示来自 TimeCard 所引用的其他模式之一的定义,它很好地演示了单个组件的自定义。PersonNameType 包含几个简单的 xs:string 元素,以及一些带有复杂结构的其他元素。教程代码中使用的测试文档恰巧不包括这种类型的 AffixAlternateScript 元素的任何实例,因此清除它们以简化生成的数据模型再合适不过。


清单 15. PersonName 模式

... ... ...

清单 16 显示了一种定义自定义以从数据模型中清除 AffixAlternateScript 元素的方法。这种方法将使用路径指定,这是一种浏览模式定义结构的类似 XPath 的方向集合。路径步骤是通过斜杠(/)字符来分隔的,并且,匹配模式定义的命名组件(全局类型、组或 attributeGroup 定义,或者不管是否是全局性质的元素或属性定义)的步骤可以使用 [@name=...] 断言(predicate)来挑选组件类型的特殊实例。


清单 16. 直接自定义不需要的组件

清单 16 中,每条路径都是从模式级别完整拼写的。您还可以在路径中使用通配符。* 通配符作为路径的一部分将匹配模式定义中的所有单个元素,而 ** 通配符将匹配模式定义中的任意数目的嵌套元素。因此不要使用 complexType[@name=PersonNameType]/sequence/element[@name=Affix] 路径,您可以转而使用 complexType[@name=PersonNameType]/*/element[@name=Affix]complexType[@name=PersonNameType]/**/element[@name=Affix]。可是,您不能使用 **/element[@name=Affix] — CodeGen 要求您明确识别任何自定义中涉及的全局定义组件,作为防止错误地应用自定义的安全措施。

只要嵌套匹配模式定义结构,那么就可以嵌套组件自定义。在这种情况下,每个自定义只需指定与包含的自定义相关的目标。您还可以在自定义中使用 name="..." 属性替代路径的最后一步中的 [@name=...] 断言,并且可以跳过最后一步的元素名称(因为它必须始终与自定义元素的名称相同)。您甚至可以完全避免使用路径,而使用结合了名称属性的嵌套。清单 17 显示的自定义与 清单 16 相同,只是进行了重构,使用了这种备选方法:


清单 17. 嵌套自定义不需要的组件

					

  
    
      
        
        
      
    
  

简化数据模型

除了在前一小节中用作示例的 PersonName 组件之外,TimeCard 模式还具有大量未在本教程的样例文档中使用的其他复杂组件。通过使用自定义清除这些未使用的组件,可以大大简化生成的数据模型。在某些情况下,CodeGen 所使用的 Java 值名称无法正常工作。尤其是重复使用同一个元素名称会导致只能通过数字后缀区分值名称,因此很难了解如何正确使用值。参见 清单 10 中的示例,其中生成的代码中包括一对名为 durationduration1 的字段。您可以使用自定义将这些名称改为更有意义的名称。

清单 18 将显示来自代码的 hrxml 目录中的 custom3.xml 文件,该文件包括所有这些自定义。该清单有意使用前一小节讨论的各种识别组件的方法,结合了嵌套、路径,以及包含有名称的路径。值名称自定义位于底部,使用 value-name="simpleDuration" 属性把第二个 duration 使用的名称改为更具描述性的形式。


清单 18. 简化 TimeCard 数据模型并使其含义更清晰

您可以使用 custgen3 Ant 目标尝试用 CodeGen 使用此自定义,或者使用 custom3 目标运行完整的生成、编译、绑定及测试。自定义将把生成的类的数量减少到 9 个顶级类和 10 个内部类,总计 19 个类。这刚好是未使用自定义的初始数据模型中的类数目的一半。

CodeGen 命令行参数

除了在教程代码中使用的那些命令行参数之外,CodeGen 还支持若干个附加命令行参数。表 1 列出了最重要的选项:


表 1. CodeGen 命令行选项

命令 用途
-c path 输入自定义文件的路径
-n package 无名称空间的模式定义的默认包(默认为默认包)
-p package 所有模式定义的默认包(默认为使用从各个模式名称空间生成的包)
-s path 模式根目录路径(默认为当前目录)
-t path 生成的输出的目标目录路径(默认为当前目录)
-v 冗余输出标志
-w 在生成输出前从目标目录中删除所有文件(如果目标目录就是当前目录,则忽略)

通过在自定义属性值之前使用特殊的 -- 前缀,您还可以将全局自定义作为命令行参数传递给 CodeGen,而无需创建自定义文件。因此,要设置 清单 5 的自定义中使用的全局选项,您需要向 CodeGen 命令行中添加 --prefer-inline=true --show-schema=false --enumeration-type=simple --generate-all=false(可是,使用这种方法您无法指定要在生成中包括的模式组件列表,因为这些模式组件是专门用于特定模式的)。在使用这项技术时,不需要对属性值使用引号。如果需要设置获取多个值的列表的自定义,则使用逗号而不要使用空格作为各个值之间的分隔符(这样将忽略 TypeGroup 模式名称后缀,例如,使用命令行 --strip-suffixes=Type,Group 参数)。

 

结束语

在本教程中,您首先了解了使用 JiBX 通过 XML 模式定义生成 Java 数据模型,然后在匹配该模式的文档与数据模型之间来回转换。还了解了如何使用自定义控制数据模型的生成方式。除了本教程中介绍的自定义之外,还有许多其他自定义可用于控制数据模型的各个方面。JiBX 文档将提供所有这些自定义选项的完整信息,以及通过模式生成代码的更多示例。

Web 服务定义是 XML 模式的主要应用之一。JiBX 目前可以在 Apache Axis2、Apache CXF、XFire 和 Spring-WS Web 服务堆栈中使用,并且它还支持它自己的 JiBX/WS 形式的轻量级 Web 服务引擎。您可以对这些 Web 服务堆栈中的任意一个使用本教程讨论的通过模式生成代码的功能,不过目前还需要先通过 Web 服务描述语言(Web Services Description Language,WSDL)服务定义提取模式定义,然后才能生成。您还需要执行针对每个堆栈的附加步骤才能得到有效的 Web 服务。JiBX 的未来版本将简化创建 Web 服务实现的过程,因此请查阅 JiBX 发行版中的文档以了解该领域中的所有新功能。

你可能感兴趣的:(XML)