内容


实用数据绑定

考察 JAXB,第 1 部分

往返和 XML 到 Java 的转换

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 实用数据绑定

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

此内容是该系列的一部分:实用数据绑定

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

在深入数据绑定的细节,尤其是探讨如何将数据绑定工具包用于一般的编程问题之前,您需要选择使用的数据绑定工具包。我的一般原则是您应该自行挑选软件,因为每个人的编程需求无疑都是唯一的。也就是说,程序员做出这些决策所用的信息是普遍适用的。本文中,我将根据这些普遍的原则分析 JAXB,并帮助确定 JAXB 是否适合您的数据绑定需求。

简述


不过在展开 JAXB 的讨论之前,我要简要地回顾一下本系列 上一篇文章中所提到的概念。其中的重要定义有:

  • 解组:把 XML 数据转化成 Java 类(或者多个类)的过程。
  • 编组:把 Java 数据转化成 XML 文档的过程(恰恰与解组相反)。
  • 语义等价:基于 XML 规则的相等。即使两个文档 看起来不同,但在语义上可能是等价的,参见上一篇文章中的例子。
  • 往返:从 XML 文档到 Java 代码然后再回到 XML 的整个过程。有效的往返保证输入和输出文档是相同的(语义等价)。

本文中将不那么严格地使用这些术语,一定要真正掌握每个概念的含义。

还应该明白,本文以及后面的几篇文章中,重点不一定是讨论基本的功能,而使这种功能的实现。煤中数据绑定工具包都能编组和解组数据。但是许多工具包不那么严格地执行这项任务,结果危害了往返的语义等价性。实现中的瑕疵(或者实现的功能不完整)是本系列中开始几篇文章的重点,因此我要用几篇文章来说明工具包的基本用法就不奇怪了,如果不知道它是否 真正有效使用一个工具包又有什么意义呢?

最后,我假设您已经安装并运行了 JAXB。您可以在 developerWorks上找到详细描述安装过程的大量文章,而且有了新的 Sun Java Web Services Developer Toolkit,安装非常简单。安装好工具包并设置正确的类路径,就万事俱备了。

生成类


使用 JAXB 进行之前,首先要生成表示 XML 数据的 Java 类。这些例子中将使用一个非常简单的 XML 文档,如清单 1 所示。这是一份吉他的简单列表,吉他是我的爱好之一。

清单 1. 简单的 XML 文档:吉他列表

<guitars>
  <guitar id="10021">
    <builder luthier="true">Ryan</builder>
    <model>Mission Grand Concert</model>
    <back-sides>Brazilian Rosewood</back-sides>
    <top>Adirondack Spruce</top>
    <notes>
      <![CDATA[
        Just unbelievable...   this guitar has all the tone & 
        resonance you could ever want. I mean, <<WOW!!!>> This 
        is a lifetime guitar.
      ]]>
    </notes>
  </guitar>
  <guitar id="0923">
    <builder smallShop="true">Bourgeois</builder>
    <model>OMC</model>
    <back-sides>Bubinga</back-sides>
    <top>Adirondack Spruce</top>
  </guitar>
  <guitar id="11091">
    <builder>Martin & Company</builder>
    <model>OM-28VR</model>
    <back-sides>Indian Rosewood</back-sides>
    <top bearclaw="true">Sitka Spruce</top>
    <notes>It's certainly true that Martin isn't the only game in town anymore. 
           Still, the OM-28VR is one of their best models...     and this one 
           has some fabulous bearclaw to boot.              Nice specimen of a 
           still-important guitar manufacturer.
    </notes>
  </guitar>
</guitars>

使用 JAXB 时还需要一个 XML Schema 以生成类和数据结构。 清单 1的 XML Schema 如清单 2 所示。

清单 2. 清单 1 的 XML Schema

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" 
           elementFormDefault="qualified">
  <xs:element name="back-sides" type="xs:string"/>
  <xs:element name="builder">
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:string">
          <xs:attribute name="luthier" default="false">
            <xs:simpleType>
              <xs:restriction base="xs:NMTOKEN">
                <xs:enumeration value="true"/>
                <xs:enumeration value="false"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
          <xs:attribute name="smallShop" default="false">
            <xs:simpleType>
              <xs:restriction base="xs:NMTOKEN">
                <xs:enumeration value="true"/>
                <xs:enumeration value="false"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
  <xs:element name="guitar">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="builder"/>
        <xs:element ref="model"/>
        <xs:element ref="back-sides"/>
        <xs:element ref="top"/>
        <xs:element ref="notes" minOccurs="0"/>
      </xs:sequence>
      <xs:attribute name="id" type="xs:string" use="required"/>
    </xs:complexType>
  </xs:element>
  <xs:element name="guitars">
    <xs:complexType>
      <xs:sequence>
        <xs:element ref="guitar" maxOccurs="unbounded"/>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
  <xs:element name="model" type="xs:string"/>
  <xs:element name="notes" type="xs:string"/>
  <xs:element name="top">
    <xs:complexType>
      <xs:simpleContent>
        <xs:extension base="xs:string">
          <xs:attribute name="bearclaw" default="false">
            <xs:simpleType>
              <xs:restriction base="xs:NMTOKEN">
                <xs:enumeration value="true"/>
                <xs:enumeration value="false"/>
              </xs:restriction>
            </xs:simpleType>
          </xs:attribute>
        </xs:extension>
      </xs:simpleContent>
    </xs:complexType>
  </xs:element>
</xs:schema>

基本步骤


准备好了 XML 和 XML Schema,生成 JAXB 类就很简单了。确认已设置好命令行和环境,然后输入以下命令:

xjc -p com.ibm.dw guitars.xsd -d src

一定要在和 guitars.xsd文件相同的目录中执行上述命令,并且在工作目录中建立一个 src目录。如果没有按这些步骤操作,就会出现某种 java.io.IOException 错误。否则应该能看到一长串的输出结果,如清单 3 所示。

清单 3. JAXB 类生成的输出结果

C:\developerworks>xjc -p com.ibm.dw guitars.xsd -d src
parsing a schema...
compiling a schema...
com\ibm\dw\impl\runtime\MSVValidator.java
com\ibm\dw\impl\runtime\SAXUnmarshallerHandlerImpl.java
com\ibm\dw\impl\runtime\ErrorHandlerAdaptor.java
com\ibm\dw\impl\runtime\AbstractUnmarshallingEventHandlerImpl.java
com\ibm\dw\impl\runtime\UnmarshallableObject.java
com\ibm\dw\impl\runtime\SAXMarshaller.java
com\ibm\dw\impl\runtime\XMLSerializer.java
com\ibm\dw\impl\runtime\ContentHandlerAdaptor.java
com\ibm\dw\impl\runtime\UnmarshallingEventHandlerAdaptor.java
com\ibm\dw\impl\runtime\SAXUnmarshallerHandler.java
com\ibm\dw\impl\runtime\ValidatorImpl.java
com\ibm\dw\impl\runtime\ValidatableObject.java
com\ibm\dw\impl\runtime\UnmarshallerImpl.java
com\ibm\dw\impl\runtime\NamespaceContext2.java
com\ibm\dw\impl\runtime\Discarder.java
com\ibm\dw\impl\runtime\NamespaceContextImpl.java
com\ibm\dw\impl\runtime\ValidatingUnmarshaller.java
com\ibm\dw\impl\runtime\UnmarshallingContext.java
com\ibm\dw\impl\runtime\GrammarInfoImpl.java
com\ibm\dw\impl\runtime\ValidationContext.java

实在是有点太多了--注意,即使对于一个相当简单的 XML Schema,JAXB 也创建了 大量的类。

对往返的影响


现在我已经介绍了基本的步骤,下面将实际分析一下其中到底发生了什么。不必浪费时间回顾 JAXB 的基础(其他文章已经做了很好的介绍),对于每个元素都有两个源文件,一个文件和元素同名(比如,Guitar.java),另一个则在元素名后面加上“Type”(如 GuitarType.java)。这两个文件都是接口,类的实现在子目录 impl 下。这样就生成了很多类--我认为有点太过分了。

但真正有意思的是这些类本身。要知道数据绑定实现的主要问题之一是往返--即从 XML 到 Java 代码再返回到 XML 的过程中数据不会发生不可预料的变化的能力。换句话说,进去的是什么出来的就是什么。在目前,您还没有准备好通过解组-编组循环测试输出的结果(尽管以后要这样做),首先来分析源代码中可能存在的潜在问题。

第一个问题出现在任何数据绑定软件包通常都会出问题的地方:类型化。即使有 XML Schema 的帮助,XML 也不一定能和 Java 类型很好的匹配。这通常意味着要损失一些数据类型信息,有可能掺入非法的数据。有时候问题出在 XML Schema 中,有时候则是因为 XML 到 Java 映射的局限性,必须仔细观察。源代码中发现的一个此类问题是 top 元素的表示。注意清单 4 中粗体显示的那一行,这是 TopType 类的源代码。

清单 4. TopType.java 的源代码

package com.ibm.dw;
public interface TopType {
    java.lang.String getValue();
    void setValue(java.lang.String value);
            java.lang.String getBearclaw();
    void setBearclaw(java.lang.String value);
}

回头再看一看源文档及其 XML Schema,很明显 bearclaw 属性的值应该是“true”或“false”。不幸的是,JAXB 没有发现这一点并使用布尔数据类型, TopType 类的这个属性可以接受任何字符串值。结果可能造成错误的数据。最终可能出现“True”、“true”、“tRUe”或者任何其他变化形式,而令使用 XML 的应用程序举止失措。换句话说,您碰到了必须解决的一个问题域。

这类问题可能有以下不同的解决办法:

  1. 手工编辑 TopType 类的源代码,只接受布尔值。
  2. TopType 方法中手工添加异常处理代码,保证只能提供可以转化成布尔值的字符串。
  3. 在 XML Schema 中创建表达布尔数据类型的新类型。

前两种选择非常明显。第三种选择也很简单,尽管 W3C 那帮人实际上应该把这一条放在规范中说明。清单 5 给出了一个简单的布尔类型定义:

清单 5. 模式中的布尔类型

<xsd:simpleType name="xsd:boolean">
  <xsd:restriction base="xsd:string">
    <xsd:enumeration value="true"/>
    <xsd:enumeration value="True"/>
    <xsd:enumeration value="TRUE"/>
    <xsd:enumeration value="false"/>
    <xsd:enumeration value="False"/>
    <xsd:enumeration value="FALSE"/>
  </xsd:restriction>
</xsd:simpleType>

看起来不错,是吧?但问题是它还不能解决这个问题。JAXB 根据 XML Schema 中 xsd:string 构造的用法,仍然会生成接受字符串参数的类。

在您准备告诉我这不成为一个问题之前,先让我说明 JAXB 通过 什么来保护您的数据。当从 Java 类编组回到 XML 时,将调用根据 XML Schema(和限制性的类型,如 清单 2清单 5所示)生成的验证方法。换句话说,如果您为 bearclaw 属性提供了一个值“foobar”,它就会被找出来。不过像“TRUe”、“fAlSe”和“tRue”这样的值--当然也不想要这种结果--在验证过程中也会被找出来。现在就需要使用 清单 5中详细定义的类型, 还要注意“true”和“false”这两个词因为大小写带来的变化。这种繁杂的工作看起来意义不大。正是这类问题使得往返非常复杂,真正实现要比说起来困难得多。这也 恰恰是在选择和使用数据绑定软件包时应该考虑到的那类问题。

更加需要关注的是,至少对我而言,这样可能造成超出单次往返过程的问题。要知道错误检查只有在编组时进行,这意味着只要还在内存中,错误数据就可以自由地存在于这些成员变量之中等待编组。另外, 任何具有有限值集的性质都存在这种问题,而不仅仅是布尔值。但其中最值得注意的问题是,有时候 XML 文档被读入、处理然后供其他应用程序使用,而不是被编组回到 XML。因此所有的应用程序都有可能在这些字段中插入错误的数据,而其他任何使用数据的应用程序都会得到那个错误数据。除非希望每次访问信息时都编组类,否则这个问题就确实存在。顺便说一句,这些问题表明数据绑定相对而言还不够成熟,而不仅仅是 JAXB。

我该怎么做?


那么您能做什么呢?首先要坚持阅读这些文章。我将详细分析 JAXB,后面还将探讨 Castor,尝试标志出那些需要注意的地方。不知道问题的关键在哪里,就不能编写防弹代码和错误检查代码;这正是本文以及后面几篇文章的核心。更重要的是,要认识到即使最好的数据绑定软件包,也需要一两个很棒的程序员增加另外的保护措施,才能使其正确地运行。

最后还要记住,数据绑定并不总是魔法子弹。我并不想打消你们对数据绑定的兴趣,恰恰相反,我认为它是一种了不起的应用程序。但是有时候一个简单的 SAX 程序或者 DOM 树就能提供需要的全部功能,就不需要再引入数据绑定项目的复杂性了。在以后的专栏中,我将分析使用数据绑定的最佳时机,什么时候使用 SAX 和 DOM 更有效,并通过大量的例子帮助您作出决策。

结束语


显然这里关于 JAXB 的分析还不够完全,但是您已经看到了研究数据绑定软件包时有价值的分析方法。选择一个数据绑定软件包要比选择喜欢的网站和单击链接复杂得多,要保证选择的应用程序能够正确处理像往返这样的问题。

JAXB 仍然不够成熟,仍然是一种非常新的技术的较早主要版本。还要记住 JAXB 以前的几个版本基本上已经废弃了(还记得当时 JAXB 只能使用 DTD 吗?它现在只能使用 XSD),因此 1.x 版是对这类问题真正的 第一次尝试。并不说不应使用 JAXB,只是说必须小心谨慎。

下一篇文章,我将从类生成转移到解组和编组,并说明它是如何工作的。我还将钻研像空格、CDATA 节以及许多其他问题的处理。请继续坚持,您将看到更多的代码、更多的细节和更多的乐趣。下一次网上见!


相关主题


评论

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=XML
ArticleID=22108
ArticleTitle=实用数据绑定: 考察 JAXB,第 1 部分
publish-date=05012004