BindGen 自定义
在本节中,您将了解如何自定义 BindGen 操作以控制数据的 XML 表示、更改名称及名称空间的样式以及控制模式结构的某些方面。
自定义 BindGen 操作
BindGen 支持对绑定和模式生成的各个方面进行大量自定义。要应用的自定义集将作为 XML 文档传递给 BindGen,该文档中拥有与 Java 代码结构对应的嵌套元素。清单 6 给出了简单示例:
清单 6. 简单的自定义示例
<custom>
<package name="org.jibx.starter" property-access="true">
<class name="Address" includes="street1 street2 city state postCode country"/>
<class name="Item" excludes="description"/>
</package>
</custom> |
本例将处理单个 Java 代码包,因此 清单 6 仅使用 <custom> 根元素的一个 <package> 子元素。<package> 和 <class> 自定义元素将使用与所有被包含的 <package> 元素相关的名称属性,因此在 清单 6 示例中,每个 <class> 元素仅需要一个简单的类名。<package> 元素可以相互嵌套,因此如果跨越包的层次结构处理类,则使用嵌套的 <package> 元素处理所有选项将十分轻松。嵌套结构尤为便利,因为正如我稍后将在本节中讨论的那样,许多自定义属性都是通过元素嵌套继承的。可是,使用嵌套是可选的 — 如果需要,您也可以完全跳过 <package> 元素,并直接使用拥有完全限定类名的 <class> 元素。
使用 -c file-path 命令格式,把自定义文件作为命令行参数传递给 BindGen。自定义永远是可选的,并且您永远不需要使用自定义文件,除非需要更改默认的 BindGen 行为。
控制 BindGen 使用代码的方式
BindGen 通过对 Java 类的默认处理可以完成某种程度的工作,但是在没有用户指导的情况下,可以完成的工作是有限的。例如,默认处理是在 XML 表示中包括除了 static、transient 或 final 字段以外的所有字段。使用这种方法对于表示简单数据对象的类来说没有问题;但是,如果类包括状态信息或计算值,则最终的 XML 表示可能包括不想公开到类之外的值。
自定义允许您通过两种方式控制 BindGen 在 XML 表示中使用的内容。首先,您可以轻松地使用 bean 样式的 getXXX()、setXXX() 和 isXXX() 访问方法,而不是直接访问字段。其次,您可以选择是列出需要在类的 XML 表示中包括的值,还是列出需要排除的值。清单 6 中的自定义演示了这两个技巧。
 |
单向转换
本教程使用 JiBX 进行 Java 数据结构与 XML 文档之间的双向转换。BindGen 还支持生成单向转换,在某些情况下生成单向转换可能会更方便。例如,值对象类通常是不可修改的,并且只定义 final 字段和读取(“get”)访问方法。这使得值对象类很难用于 BindGen 的双向转换。如果改为告诉 BindGen 生成仅执行输出的转换,它将轻松地处理您需要的字段或属性。要生成单向转换,请在自定义的根 <custom> 元素中使用 direction="output" 或 direction="input"。
|
|
清单 6 中的 <package> 元素将使用 property-access="true" 属性来告诉 BindGen,当确定要在 XML 表示中包含哪些值时,查找公共的非静态访问方法定义的 bean 样式的属性,而不是查找字段定义的 bean 样式的属性。此属性是继承自定义设置的一个例子,继承的自定义设置将应用到带有该属性的元素内嵌套的所有内容中。在 清单 6 示例中,设置将应用到两个嵌套的 <class> 元素中。它还将应用到包中的所有其他类中,即使其他类中没有 <class> 自定义元素。除了确定如何从类表示中找到值之外,property-access 设置还将控制如何通过生成的 JiBX 代码访问值 — 直接通过字段还是通过调用访问方法。
清单 6 中的第一个 <class> 元素将使用 includes="street1 street2 city state postCode country" 属性列出 BindGen 需要包含在 XML 表示中的类的特定值。清单中的第二个 <class> 元素将使用 excludes="description" 属性,该属性将列出要从 XML 表示中排除的值。由于对于值使用的是属性访问而非字段访问,因此这些名称与 get/set/is 访问方法所定义的属性相匹配。如果使用字段,则值名称将与字段名称相匹配。
custgen1 Ant 目标将使用 清单 6 中的自定义操作运行 BindGen。此目标是先前所示的 bindgen 目标的替代目标,因此要运行完整的构建,您最好使用 Ant 命令行:ant compile custgen1 bind。清单 7 显示了在此目标运行时生成的模式中的项目类型定义:
清单 7. 自定义模式输出片段
<xs:element name="item" minOccurs="0" maxOccurs="unbounded">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:string" name="id" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:int" use="required" name="quantity"/>
<xs:attribute type="xs:float" use="required" name="price"/>
</xs:complexType>
</xs:element> |
您可以从 清单 7 中看出,模式表示现在缺少了描述值,正如自定义指定的那样。
在使用此自定义时,您需要使用其他 XML 文档作为输入,因为不再使用初始 XML 中提供的 <description> 元素。Ant run1 目标将运行使用 data1.xml 作为输入并生成 out1.xml 作为输出的测试程序。您还可以用 custom1 Ant 目标运行从编译源代码到运行测试程序的整个过程。
控制实例创建
还可以使用自定义控制解组期间的实例创建。默认情况下,JiBX 期望为每个类定义无实参(默认)的构造函数(Java 编译器将自动生成这些构造函数,如果您不定义任何其他构造函数的话)。在解组过程中需要类的新实例时,JiBX 将使用无实参构造函数创建实例。如果您的某些类只定义带有实参的构造函数,则可以使用 BindGen 自定义,将这些构造函数变成 JiBX 可以使用的构造函数。实现此目的一种方法是定义创建类实例时使用的工厂方法,其中在 <class> 自定义元素中使用 factory="xxx" 属性,以提供返回类实例的静态方法的完全限定名称(带有前导包和类信息)。您还可以在根 <custom> 元素中添加 add-constructors="true",它将生成一个根据需要向类中添加无实参构造函数的绑定。这第二种方法对于普通数据类是起作用的,但是您仍然需要提供所有实例或抽象类的工厂(因为它是绝不可以直接构造的)。当然,如果要生成仅执行输出的绑定(请参阅 单向转换 侧栏),则实例创建不成问题,并且您不需要考虑构造函数。
处理输入类的其他自定义
BindGen 支持许多其他类型的自定义,用于控制 BindGen 处理 Java 输入类的方式。例如,如果对 Java 字段名使用命名约定,则可以通过使用 strip-prefixes 或 strip-suffixes 属性将 BindGen 配置为忽略特殊的前缀或后缀字符串(例如,如果要忽略前导的 m_ 和 s_ 前缀,则需使用 strip-prefixes="m_ s_")。这些对字段名的修改需要在将字段匹配到其他自定义中使用的值名称之前应用,并且在通过字段名生成 XML 名称时也将应用这些修改。
您还可以使用嵌套的 <value> 元素自定义类中各个字段或 bean 属性的处理方式。您将在后面的示例中看到如何使用这些值自定义元素。
控制 XML 表示
除了控制 BindGen 解析 Java 代码的方法之外,您还可以使用自定义控制数据的 XML 表示。无论值是可选的还是必要的,这些 XML 自定义都包括值的实际表示(作为元素或属性)、元素和属性的顺序和名称等等。
前面 清单 6 中的自定义示例演示了对于第一个 <class> 元素使用的 includes="street1 street2 city state postCode country" 属性形式的一个 XML 自定义。我讨论了此自定义如何从类中选择包含在 XML 表示中的值。它还将控制 XML 表示,因为列出值的顺序将成为它们在 XML 表示中的顺序。这对于在 XML 中总是被视为无序的属性来说不是重要的问题,但是对于元素来说十分重要。
如果没有 在 <class> 自定义中使用 includes 属性指定值的顺序,BindGen 将对类使用 Java 反射,按照值的分发顺序生成值。对于大多数 Java 编译器和 JVM,此反射顺序将与 Java 源代码中的定义顺序将匹配。但是,Java 编译器和 JVM 不是必须 要保留来自源代码的这个顺序,因此某些编译器或 JVM 可能导致 BindGen 更改子元素的顺序。如果您希望在无论使用哪个 Java 编译器和 JVM 的情况下都确保 XML 表示始终相同,使用 includes 属性可以帮助您轻松地固定顺序。
您还可以使用 includes 属性控制值的 XML 表示。BindGen 允许在列表中的各个名称上使用前导标志字符以指名表示:@ 表示属性,/ 表示属性。因此如果把 清单 6 的自定义更改为 includes="street1 street2 city state @postCode country",则邮政编码值的表示将从子元素变为属性。
控制必需状态
另一项轻松的自定义是使用 <class> 元素的 requireds 和 optionals 属性,可以控制是否将值设为可选值,还是设为必需值。和使用 includes 属性一样,您可以在 requireds 和 optionals 列表中的名称前面加一个标志字符,表示是将它们设为子元素,还是设为属性。
默认情况下,BindGen 将把所有原语值和简单对象值(带有直接的 XML 等效类型而非 String 的类)处理为属性,并把所有复杂对象值处理为子元素。所有原语值都将被处理为必需值,而所有对象值都将被处理为可选值。除了通过使用 includes、requireds 和 optionals 元素在 <class> 自定义级别覆盖这些默认值之外,您还可以通过在任意自定义级别(<custom>、<package> 或 <class> 元素)中设置 value-style="element" 属性,将默认表示改为对所有值使用元素。您还可以使用 require 属性来控制:在 XML 中,哪些类型应当被处理为必需值:
-
require="none" 将把??有内容都处理为可选值。
-
require="primitives" 是默认值,仅将原语值处理为必需值。
-
require="objects" 与默认值相反,将把原语值设为可选值,将对象类型处理为必需值。
-
require="all" 将把所有值默认处理为必需值。
清单 8 显示了教程下载部分的 dwcode1 目录中的 custom2.xml 自定义文件,演示了我在本节中讨论过的几项功能:
清单 8. 自定义顺序、必需状态及表示
<custom property-access="true">
<package name="org.jibx.starter">
<class name="Address" includes="street1 street2 city @state @postCode country"
requireds="street1 city"/>
<class name="Customer" includes="customerNumber firstName lastName"
requireds="lastName firstName /customerNumber"/>
<class name="Item" excludes="description" requireds="@id quantity price"/>
<class name="Order" requireds="/orderNumber customer billTo shipping orderDate"/>
</package>
</custom> |
您可以通过使用 Ant custgen2 目标(ant compile custgen2 bind,用于运行完整的构建)尝试这组自定义。清单 9 显示了使用这些自定义的生成模式的一部分,显示了结果顺序、必需状态(minOccurs="0" 在模式中被默认为是必需的,表示可选的元素,use="required" 在模式中被默认为是可选的,表示必需的属性)和元素或属性表示:
清单 9. 使用自定义生成的模式
<xs:complexType name="order">
<xs:annotation>
<xs:documentation>Order information.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element type="xs:long" name="orderNumber">
<xs:annotation>
<xs:documentation>Get the order number.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="customer">
<xs:complexType>
<xs:sequence>
<xs:element type="xs:long" name="customerNumber"/>
<xs:element type="xs:string" name="firstName"/>
<xs:element type="xs:string" name="lastName"/>
</xs:sequence>
</xs:complexType>
</xs:element>
...
</xs:sequence>
<xs:attribute type="xs:date" use="required" name="orderDate"/>
<xs:attribute type="xs:date" name="shipDate"/>
<xs:attribute type="xs:float" name="total"/>
</xs:complexType>
<xs:element type="tns:order" name="order"/>
<xs:complexType name="address">
<xs:annotation>
<xs:documentation>Address information.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element type="xs:string" name="street1"/>
<xs:element type="xs:string" name="street2" minOccurs="0"/>
<xs:element type="xs:string" name="city"/>
<xs:element type="xs:string" name="country" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="state"/>
<xs:attribute type="xs:string" name="postCode"/>
</xs:complexType> |
在使用 bind Ant 任务编译绑定之后,您可以使用以 data2.xml 测试文档为输入并生成输出 out2.xml 的 run2 任务测试此模式。您还可以从编译开始运行整个过程以用 custom2 目标进行测试。清单 10 显示了测试文档:
清单 10. 匹配自定义的测试文档
<order orderDate="2008-10-18" shipDate="2008-10-22" xmlns="http://jibx.org/starter">
<orderNumber>12345678</orderNumber>
<customer>
<customerNumber>5678</customerNumber>
<firstName>John</firstName>
<lastName>Smith</lastName>
</customer>
<billTo state="WA" postCode="98059">
<street1>12345 Happy Lane</street1>
<city>Plunk</city>
<country>USA</country>
</billTo>
<shipping>PRIORITY_MAIL</shipping>
<shipTo state="WA" postCode="98034">
<street1>333 River Avenue</street1>
<city>Kirkland</city>
</shipTo>
<item quantity="1" price="5.99" id="8394983498512"/>
<item quantity="2" price="9.50" id="9912349050499"/>
<item quantity="1" price="8.95" id="1293000488209"/>
</order> |
比较 清单 10 与 清单 3 所示的初始测试文档,看一看自定义如何改变数据的 XML 表示(包括将排列项表示改为空元素,这是一种比原来更紧凑的表示)。
控制名称和名称空间
Java 名称通常使用 “大小写混合" 样式:名称大部分是小写的,但是每个单词的首字母是大写的。对于字段或属性名称,首字母大写只适用于第一个单词之后的单词(得到诸如 postCode 和 customerNumber 之类的名称)。XML 名称不是标准化的,并且通常使用几种不同的样式。这些样式包括首字母小写的大小写混合样式(Java 字段和属性名称样式)、首字母大写的大小写混合(Java 类名样式)、连字符分隔符(用连字符分隔单词)样式、点分隔符(用点分隔单词)样式以及下划线分隔符(用下划线分隔单词)样式。
默认情况下,BindGen 对 XML 名称采取大小写混合样式,但是您可以通过在任意自定义级别中设置 name-style 属性(<custom>、<package> 或 <class> 元素)轻松地更改此样式。此属性允许的值与上面列出的各种 XML 样式相匹配:
-
camel-case(默认)
-
upper-camel-case
-
hyphenated
-
dotted
-
underscored
您还可以使用专门针对值的自定义设置该值的 XML 名称。使用单独的值自定义将使您可以完全控制该值的访问方法及在 XML 中的表示方法。基于您已经在前面示例中看到的相同示例代码,清单 11 将给出若干个将自定义元素用于单独值的示例:
清单 11. 自定义名称和名称空间
<custom property-access="true" name-style="hyphenated" namespace="http://jibx.org/custom"
namespace-style="fixed">
<package name="org.jibx.starter">
<class name="Address" includes="street1 street2 city @state @postCode country"
requireds="street1 city"/>
<class name="Customer" includes="customerNumber firstName lastName"
requireds="lastName firstName /customerNumber"/>
<class name="Item" excludes="description" requireds="@id quantity price"/>
<class name="Order" requireds="orderNumber customer billTo shipping orderDate">
<value property-name="orderNumber" element="order-num"/>
<value property-name="items" item-name="line-item" element="order-items"/>
</class>
</package>
</custom> |
清单 11 中的第一个值自定义用于 <class name="Order"...> 元素中的 orderNumber 属性。通过使用 element="order-num" 属性,orderNumber 自定义将告诉 BindGen 将值表示为元素,而不是原语值使用的默认属性形式。第二个自定义用于 items 集合属性。此自定义将使用 item-name 和 element 属性。item-name 属性将控制集合所表示的各个值使用的名称,而 element 属性将强制使用提供的名称作为集合中的值的封装元素。
 |
没有名称空间的 XML
所有教程示例都使用 XML 名称空间,因为通常把使用名称空间视为数据交换的最佳实践。如果需要使用没有名称空间的 XML,您可以在任意自定义级别使用 namespace-style="none" 属性彻底关闭所有嵌套组件的名称空间。
|
|
清单 11 中的自定义还定义要在 XML 文档中使用的名称空间。前面的示例依赖于 BindGen 对名称空间的默认处理:从 Java 包派生出在 Java 代码的 XML 表示中使用的名称空间 URI。此默认处理把 org.jibx.starter 包转换成了名称空间 URI http://jibx.org/starter。在 清单 11 中,名称空间是通过在根 <custom> 元素中添加一对属性 —
namespace="http://jibx.org/custom" 和 namespace-style="fixed"
— 来自定义的。这些属性中的第一个属性将定义基本名称空间,而第二个属性将防止根据 Java 包修改名称空间的一般行为。这些属性都是通过嵌套自定义元素继承的,因此可以将其轻松地放到 <package> 元素中,而不是放在 <custom> 元素中。
您可以通过使用 Ant custgen3 目标生成绑定和模式,并使用 run3 目标运行测试(在使用标准 bind 目标运行 JiBX 绑定编译器后 — 或者使用 full3 目标执行整个过程),尝试执行 清单 11 中的自定义。清单 12 显示了用于测试代码的输入文档:
清单 12. 带有自定义名称及名称空间的 XML 样例
<order order-date="2008-10-18" ship-date="2008-10-22" xmlns="http://jibx.org/custom">
<order-num>12345678</order-num>
<customer>
<customer-number>5678</customer-number>
<first-name>John</first-name>
<last-name>Smith</last-name>
</customer>
<bill-to state="WA" post-code="98059">
<street1>12345 Happy Lane</street1>
<city>Plunk</city>
<country>USA</country>
</bill-to>
<shipping>PRIORITY_MAIL</shipping>
<ship-to state="WA" postCode="98034">
<street1>333 River Avenue</street1>
<city>Kirkland</city>
</ship-to>
<order-items>
<line-item quantity="1" price="5.99" id="AC4983498512"/>
<line-item quantity="2" price="9.50" id="IW2349050499"/>
<line-item quantity="1" price="8.95" id="RC3000488209"/>
</order-items>
</order> |
如果比较 清单 12 与 清单 10 中的样例,您将看到最新的自定义如何更改了表示。
自定义模式表示
现在,您已经了解了 BindGen 自定义如何更改 Java 数据的 XML 表示。自定义还可用于控制实际模式结构的某些方面。
回想一下,BindGen 默认对全局类型和元素优先使用嵌套定义。如果回顾 清单 9 生成的模式,您将看到此嵌套结构。模式仅使用三个全局定义:address 和 order 复杂类型以及 order 元素。Java 数据结构中的其他类(Customer、Item 和 Shipping)都只在 Order 类中引用一次,因此相应的类型定义是直接嵌入在 order 模式类型定义中的。
您可以在任意嵌套自定义元素中使用 force-mapping="true" 属性来更改模式样式。清单 13 显示了 custom4.xml 自定义文件,该文件将把此更改添加到匹配 清单 9 生成的模式的 custom2.xml 自定义中:
清单 13. 模式结构的自定义
<custom property-access="true" force-mapping="true">
<package name="org.jibx.starter">
<class name="Address" includes="street1 street2 city @state @postCode country"
requireds="street1 city"/>
<class name="Customer" includes="customerNumber firstName lastName"
requireds="lastName firstName /customerNumber"/>
<class name="Item" excludes="description" requireds="@id quantity price"/>
<class name="Order" requireds="/orderNumber customer billTo shipping orderDate"/>
</package>
</custom> |
清单 14 显示了得到的模式结构(通过运行 custgen4 Ant 目标生成为 starter.xsd)。此版本的模式表示的 XML 文档结构与 清单 9 模式相同,但是包括匹配每个 Java 类的单独类型定义。
清单 14. 自定义的模式结构
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://jibx.org/starter" elementFormDefault="qualified"
targetNamespace="http://jibx.org/starter">
<xs:simpleType name="shipping">
<xs:annotation>
<xs:documentation>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.</xs:documentation>
</xs:annotation>
<xs:restriction base="xs:string">
...
</xs:restriction>
</xs:simpleType>
<xs:complexType name="item">
<xs:annotation>
<xs:documentation>Order line item information.</xs:documentation>
</xs:annotation>
<xs:sequence/>
<xs:attribute type="xs:string" use="required" name="id"/>
<xs:attribute type="xs:int" use="required" name="quantity"/>
<xs:attribute type="xs:float" use="required" name="price"/>
</xs:complexType>
<xs:element type="tns:order" name="order"/>
<xs:complexType name="address">
<xs:annotation>
<xs:documentation>Address information.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element type="xs:string" name="street1"/>
<xs:element type="xs:string" name="street2" minOccurs="0"/>
<xs:element type="xs:string" name="city"/>
<xs:element type="xs:string" name="country" minOccurs="0"/>
</xs:sequence>
<xs:attribute type="xs:string" name="state"/>
<xs:attribute type="xs:string" name="postCode"/>
</xs:complexType>
<xs:complexType name="customer">
<xs:annotation>
<xs:documentation>Customer information.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element type="xs:long" name="customerNumber"/>
<xs:element type="xs:string" name="firstName"/>
<xs:element type="xs:string" name="lastName"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="order">
<xs:annotation>
<xs:documentation>Order information.</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element type="xs:long" name="orderNumber">
<xs:annotation>
<xs:documentation>Get the order number.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element type="tns:customer" name="customer"/>
<xs:element type="tns:address" name="billTo"/>
<xs:element type="tns:shipping" name="shipping"/>
<xs:element type="tns:address" name="shipTo" minOccurs="0"/>
<xs:element type="tns:item" name="item" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute type="xs:date" use="required" name="orderDate"/>
<xs:attribute type="xs:date" name="shipDate"/>
<xs:attribute type="xs:float" name="total"/>
</xs:complexType>
</xs:schema> |
清单 14 所示的类型模式称为 “Venetian
Blind” 样式模式,常用于复杂的 XML 结构定义。通过分隔各个类型定义,这种模式样式使您可以在修改或扩展模式时轻松地重用组件结构。如果只是计划使用 Java 代码作为进一步更改的基础(每次代码更改时都要再次运行 BindGen),则 Venetian Blind 样式的灵活性可能并不重要,但是如果想要使用模式作为以后开发的基础,则非常适合使用 Venetian Blind。
|