往返和 XML 到 Java 的转换
Brett McLaughlin (
[email protected]), 编辑, O'Reilly and Associates
Brett McLaughlin 从 Logo 时代(还记得那个小三角吗?)就开始从事计算机。他目前专门使用 Java 相关技术构建应用程序基础设施。他最近几年为 Nextel Communications 和 Allegiance Telecom, Inc. 实现了这些基础设施。Brett 是 Java Apache 项目 Turbine 的缔造者之一,该项目使用 Java servlet 为 Web 应用程序开发建立了可重用的组件体系结构。他还参与了 EJBoss 项目(一种开放源代码的 EJB 应用程序服务器)和 Cocoon(一种开放源代码的 XML Web 发布引擎)。
简介: 上一期文章中,Brett 分析了数据绑定中的几个重要概念,包括往返和语义等价。他在本文中按照这些术语考察了 Sun JAXB 的体系结构和实现。您将了解到 JAXB 如何处理类的生成,以及对 API 接受的 XML 输入和输出有什么样的影响。
标记本文!
发布日期: 2004 年 5 月 01 日
级别: 初级
访问情况 418 次浏览
建议: 0 (添加评论)
平均分 (共 1 个评分 )
在深入数据绑定的细节,尤其是探讨如何将数据绑定工具包用于一般的编程问题之前,您需要选择使用的数据绑定工具包。我的一般原则是您应该自行挑选软件,因为每个人的编程需求无疑都是唯一的。也就是说,程序员做出这些决策所用的信息是普遍适用的。本文中,我将根据这些普遍的原则分析 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 的应用程序举止失措。换句话说,您碰到了必须解决的一个问题域。
这类问题可能有以下不同的解决办法:
手工编辑 TopType 类的源代码,只接受布尔值。
向 TopType 方法中手工添加异常处理代码,保证只能提供可以转化成布尔值的字符串。
在 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 节以及许多其他问题的处理。请继续坚持,您将看到更多的代码、更多的细节和更多的乐趣。下一次网上见!