SUN 的 JAXB,即 Java API for XML,曾经受到广泛的批评,这并不是一个秘密。在早期的版本(beta 或者其他某个版本)中,它是基于 DTD 的,完全不支持 W3C XML Schema。这种状态持续了一年多,然后,JAXB 突然发布了 1.0 版。虽然这个版本解决了模式的支持问题,却完全抛弃了 DTD,开发人员编写的代码(当然是对 beta 软件)突然不能使用了。近来,JAXB 得到了一些支持,但它仍然是一个非常封闭的环境,并在实现中留下许多很深的隐患。
本文将介绍的 JaxMe 项目保留了 JAXB 的许多好的特性,并克服了它的很多不足。首先 JaxMe 是源代码开放的(在 Apache 的大伞之下),这意味着即使它现在就消失了,开发人员也仍然能够使用甚至修改它的源代码,从而保证依赖于它的那些代码仍然能够很好地运行。如果说这一点还不够,JaxMe 还提供了数据库交互、对 Enterprise JavaBeans 的支持等很多好的特性。
为了帮助您使用 JaxMe,我将详细地介绍类生成的方法。虽然这只是一项非常基本的任务,但可以帮助您熟悉 JaxMe,从而可以了解那些更有趣的特性。。
设置 JaxMe 非常轻松。请访问 JaxMe 项目站点(请参阅 参考资料链接),并从 Apache 镜像站点下载其二进制形式。在撰写本文的时候,我下载的文件是 incubated-jaxme-0.2-bin.tar.gz。(在发布本文之前刚刚出现了 0.3 版,方法一样,只不过文件名改成了 incubated-jaxme-0.3-bin.tar.gz)。在您的开发机器上对该文件进行解压缩。虽然可以在命令行中使用 JaxME,但是这样做太痛苦了(因为有 太多的JAR 文件),本文使用 Ant 来处理 JaxMe 任务。强烈建议您也这样做,这里包括所有相关的 Ant 文件,而且很容易根据需要进行修改。
和 Jaxb 一样,在用 JaxMe 做某些工作之前需要一些 XML。清单 1 是一个非常简单的 XML 模式,它定义了一个学生。当然这里的定义很不完善,但是为了把精力集中到 JaxMe 而不是模式的语义上,有必要保持示例的简单性。
清单 1. 简单的学生模式
<?xml version="1.0" encoding="UTF-8"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
xml:lang="EN"
targetNamespace="http://dw.ibm.com/jaxme/student"
xmlns:stu="http://dw.ibm.com/jaxme/student"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
>
<element name="Student">
<complexType>
<sequence>
<element name="firstName" type="string" />
<element name="lastName" type="string" />
<element name="address" type="stu:Address" />
</sequence>
</complexType>
</element>
<complexType name="Address">
<sequence>
<element name="name" type="string"/>
<element name="street" type="string"/>
<element name="city" type="string"/>
<element name="state" type="string"/>
<element name="zip" type="positiveInteger"/>
</sequence>
</complexType>
<complexType name="College">
<sequence>
<element name="name" type="string" />
<element name="address" type="stu:Address" />
</sequence>
</complexType>
</schema>
|
如果已经正确地设置了 Ant 构建过程(本文的
最后还会详细加以说明),只需输入
ant generate 就可以从这个模式生成类。我将这些细节留在了文章的最后,以便在阅读本文的过程中您能够把精力放在 JaxMe 及其语义上,最后再专门看一看 Ant。实际上,我建议您先通读一遍本文,然后再逐段地测试代码。这样,在您实际输入代码的时候,就已经掌握了其中的概念,可以更快地解决其中遇到的问题。
所有生成的类都将放在
targetNamespace 属性指定的包中。这种约定是 JaxMe 独有的,因此,您应该好好地理解它的工作原理。看一看作为该属性参数提供的 URI:
http://dw.ibm.com/jaxme/student 。这个字符串将被 JaxMe 模式编译器转化成包的名称。首先去掉“http://”,然后
逆转URI 中的主机名部分(这里就是“dw.ibm.com”),得到“com.ibm.dw”。这看起来有点奇怪,但实际上是通常的打包机制,对于用于开发特定站点或者 Bean(特别是标签库的包),这非常合理。
最后,URI 的剩余部分用斜杠(
/ )分隔,并将它们附加到从主机名派生的包名的后面。因此对于清单 1 中的模式,完整的包就是
com.ibm.dw.jaxme.student 。这个模式生成的所有类都将放在这个包中。
除了为 JaxMe 提供信息之外,
targetNamespace 属性对于 XML 还有一些特殊的意义。它告诉模式处理程序将所有创建的构造(如
Address 这样的复杂类型)都放在该名称空间中。这意味着您需要通过这个名称空间引用这些构造,如果名称空间很长,则应该定义一个前缀映射到该名称空间。
注意:如果不能理解上面那一句话,您可能需要在学习一下 XML,特别是如何将 XML Schema 用于 XML。有关的更多信息,请参阅文章后面的 参考资料(以及 developerWorks XML 专区的其他文章)。现在不妨继续读下去,请相信我,但是要成为一名 JaxMe 专家,就必须花一些时间完全理解名称空间。
在
清单 1 的模式中,引用是通过
stu 前缀实现的。通过这个前缀,您可以很容易定义类型,然后通过模式引用它们(使用名称空间前缀)。
还应该知道,JaxMe 大量使用
include 指令(该例中未使用)。特别是对于大型模式,您可以将这些定义分段放在不同的文件中,然后在最高层的模式中使用下面方式引用它们:
<!-- Include definitions from another XML Schema --> <include schemaLocation="file:///dev/jaxme/supplemental/datatypes.xsd"/> |
XML 模式处理程序将信息转移给 JaxMe,而对这些文件完全不作区分,因此我们建议,如果需要就使用多个模式。
生成类之后,花点时间熟悉一下生成的结果。虽然这些类与 JAXB 创建的构造非常类似,但也存在少量细微的差别。
它表示一个 XML 元素(来自
清单 1),是类层次的基本结构。实际上,它只是一个简单的接口,扩展了
StudentType (由 JaxMe 生成)和
Element ,后者是 JaxMe 运行时 API 的一部分。当然,这个类(以及所有其他生成的类)都在
com.ibm.dw.jaxme.student 包中。
JaxMe 中任何名为
XXXType (其中
XXX 是像“Student”或“Address”这样的名称)的对象都是从模式中派生的定义。清单 2 显示了这个类的源代码,其中的内容非常简单。
清单 2. 生成的 StudentType 代码
package com.ibm.dw.jaxme.student;
public interface StudentType {
public String getFirstName();
public void setFirstName(String pFirstName);
public String getLastName();
public void setLastName(String pLastName);
public AddressType getAddress();
public void setAddress(AddressType pAddress);
}
|
所有的属性都有访问(
getXXX() )和修改(
setXXX() )方法。当然,无论是简单字符串类型和更加复杂的类型,如
Address ,这些类型都用类表示,后面带有“Type”,因此在生成的清单中会看到对
AddressType 的引用。
AddressType.java 和 CollegeType.java
现在您已经看到了 清单 2和 StudentType.java,这些代码非常简单,您可以自行加以分析。
该文件在很多方面是 JaxMe 和特定于域的类之间的桥梁,最重要的方法有:
-
newInstance()在特定于域的上下文中创建一个新元素并返回它。 -
createStudentType()新创建的顶级Student元素。
非常奇怪的是,任何 JaxMe 文档中都没有提到该文件,提供的示例中也没有使用它。我个人倒是发现了它的某些用处,但是不建议您依赖该文件。不需要它也能完成所有的工作,如果有意在文档中忽略它,那么很可能在将来的版本中该文件就不会再出现。
该 XML 文件处理从 XML 到 Java 代码(或者相反)的映射。它将元素和类、字段和属性等联系起来。清单 3 给出了该例中相当简单的映射文件。
清单 3. 样例代码的 Configuration.xml
<Configuration xmlns="http://ws.apache.org/jaxme/namespaces/jaxme2/configuration">
<Manager validatorClass="com.ibm.dw.jaxme.student.impl.StudentTypeValidator"
qName="{http://dw.ibm.com/jaxme/student}Student"
marshallerClass="com.ibm.dw.jaxme.student.impl.StudentTypeSerializer"
handlerClass="com.ibm.dw.jaxme.student.impl.StudentHandler"
elementInterface="com.ibm.dw.jaxme.student.Student"
elementClass="com.ibm.dw.jaxme.student.impl.StudentImpl"/>
<Manager validatorClass="com.ibm.dw.jaxme.student.impl.AddressTypeValidator"
marshallerClass="com.ibm.dw.jaxme.student.impl.AddressTypeSerializer"
handlerClass="com.ibm.dw.jaxme.student.impl.AddressTypeHandler"
elementInterface="com.ibm.dw.jaxme.student.AddressType"
elementClass="com.ibm.dw.jaxme.student.impl.AddressTypeImpl"/>
<Manager validatorClass="com.ibm.dw.jaxme.student.impl.CollegeTypeValidator"
marshallerClass="com.ibm.dw.jaxme.student.impl.CollegeTypeSerializer"
handlerClass="com.ibm.dw.jaxme.student.impl.CollegeTypeHandler"
elementInterface="com.ibm.dw.jaxme.student.CollegeType"
elementClass="com.ibm.dw.jaxme.student.impl.CollegeTypeImpl"/>
</Configuration>
|
目前,JaxMe 要求直接修改该文件来改变映射。最常见的变化是您可能希望用自己的类代替生成的类。当然,您必须(至少对 JaxMe 的当前版本而言)生成默认的类, 然后再修改该文件。同样值得注意的是,实际上这种行为没有得到支持,尽管映射支持这样做,但是没有经过很好的测试,也没有很好的文档说明。
提示:将来的文章中我可能还要详细地讨论这种行为。如果对此感兴趣,请给我发电子邮件或者上传对本文的反馈意见,让我知道!这是了解对特定主题的兴趣要求的最好办法。
这是标准的 JAXB 性质文件,当然也经过了 JaxMe 的修改。其中只有一行,告诉 JAXB 工厂类使用 JaxMe 的实现类,如下所示:
javax.xml.bind.context.factory=org.apache.ws.jaxme.impl.JAXBContextImpl |
对熟悉 JAXP 的读者而言,在实现方式上,这与 Xerces 要求 JAXP 体系结构完成其实现是完全相同的。
impl 子目录中的各个源文件都是由 JaxMe 创建的接口的具体实现。一般来说不用关心这些文件,它们处理文档的 XML 加工过程,将文档转换成 Java 中的等价物。
如果您喜欢刨根问底,那么可以告诉您这些类都是 SAX 类,因为 JaxMe 使用 SAX 解析 XML。事实上,Type 类(间接地)实现了 SAX 的
org.xml.sax.ContentHandler 接口。在源代码中您还会看到
startDocument() 和
characters() 这样的方法(都列在这里的话太长了)。
虽然您可能不想对这些类太费心思,但是对其有个基本的了解还是不错的。您将会在代码中使用它们(很快就会看到),您会发现理解这些类对于查错和调试很有帮助。
观察这些类的最后一个步骤就是编译它们。这似乎是显而易见的事,但是您恐怕难以相信那些因缺乏编译而造成的问题,或者类路径问题(稍后还会提到)。因此在使用这些类之前一定要编译它们。同样,我发现使用 Ant 来完成这项工作最简单,因此我使用
ant compile 来完成这项任务。
对于喜欢苦差事(或者只是爱好输入
javac )的人,类路径中一定要包括
jaxme-api.jar和
jaxme.jar。
jaxme-api.jar包含 JAXB API,而
jaxme.jar 则包含 JaxMe 实现类。在输出的时候,应该将所有的输出都放在基目录和编译后的
impl 目录中。最后,您可能希望复制所用的支持文件:
Configuration.xml和
jaxb.properties。在运行时导入这些文件,以便对它们进行编组和解组。
您会发现没有什么好的替代构建工具。好的工具可以为您节约反复处理类路径问题的大量时间,也不用您去记住特定的命令行选项。本文中遇到的几个问题都可以使用 Ant 来自动处理。我想花一点让您看一看我的 Ant 文件,这样您也可以把 Ant 结合到您的构建环境中。我以后的文章中也可以略过这些细节(艺多不压身,是不是?)。
首先,您会希望使用 JaxMe 模式编译器/类生成工具,用
org.apache.ws.jaxme.generator.Generator 接口表示。因此,您完全可以使用 Ant 的
java 目标创建该接口的一个实例。但是,这样做有点麻烦——需要在实现中进行硬编码,而且必须将构建文件和实现的变化混在一起。您可以将实现类定义为属性,但是这样仍然存在非常低层的编码混合问题。对于使用 Ant 的人来说,所幸的是 JaxMe 包括用于在构建文件中插入类生成的 Ant
taskdef (任务定义),它可以处理所有这些细节问题。假设您有一个定制的任务定义,如清单 4 所示:
清单 4. JaxMe 的 Ant taskdef
<path id="classpath.schema-generator">
<pathelement location="${lib}/jaxme2.jar" />
<pathelement location="${lib}/jaxmejs.jar" />
<pathelement location="${lib}/jaxmexs.jar" />
<pathelement location="${lib}/jaxmeapi.jar" />
</path>
<taskdef name="xjc"
classname="org.apache.ws.jaxme.generator.XJCTask"
classpathref="classpath.schema-generator"
/>
|
只要将这个片段插入 Ant 构建文件,生成类就变得非常简单。您可以使用清单 5 中的 XML 完成这项工作。
清单 5. 生成类
<target name="generate" depends="init">
<xjc schema="student.xsd"
target="${dir.generated}">
<produces includes="com/ibm/dw/jaxme/student/*.java" />
</xjc>
</target>
|
生成类之后,现在需要编译并复制支持文件。清单 6 负责这项任务,它甚至还处理了类路径问题。
清单 6. 编译类
<path id="classpath.schema-compiler">
<pathelement location="${lib.jaxme}/jaxmeapi.jar" />
<pathelement location="${lib.jaxme}/jaxme2.jar" />
</path>
<target name="compile" depends="generate">
<javac srcdir="${dir.generated}"
destdir="${dir.build}">
<classpath refid="classpath.schema-compiler" />
</javac>
<!-- Copy over support files -->
<copy todir="${dir.build}">
<fileset dir="${dir.generated}">
<include name="**/jaxb.properties" />
<include name="**/Configuration.xml" />
</fileset>
</copy>
</target>
|
显然,如果能够清除所做的工作,然后重新开始新的工作常常是有益的。虽然这并非 JaxMe 特有的,但也值得看一看。通常的办法是通过一个称为
clean 的目标(target)进行清理,如清单 7 所示。
清单 7. 清理
<target name="clean">
<delete dir="${dir.generated}" />
<delete dir="${dir.build}" />
</target>
|
清单 8 是一个更大的 Ant 文件,它将这些成分集中到了一起。实际上这也是我一直在用的文件,该文件对于这里描述的所有内容都适用。
清单 8. JaxMe Ant taskdef
<?xml version="1.0" encoding="UTF-8"?>
<project basedir=".">
<property name="lib.jaxme" value="/Users/bmclaugh/dev/lib" />
<property name="dir.build" value="build" />
<property name="dir.generated" value="generated" />
<path id="classpath.schema-generator">
<pathelement location="${lib.jaxme}/jaxme2.jar" />
<pathelement location="${lib.jaxme}/jaxmejs.jar" />
<pathelement location="${lib.jaxme}/jaxmexs.jar" />
<pathelement location="${lib.jaxme}/jaxmeapi.jar" />
</path>
<path id="classpath.schema-compiler">
<pathelement location="${lib.jaxme}/jaxmeapi.jar" />
<pathelement location="${lib.jaxme}/jaxme2.jar" />
</path>
<taskdef name="xjc"
classname="org.apache.ws.jaxme.generator.XJCTask"
classpathref="classpath.schema-generator"
/>
<target name="init">
<mkdir dir="${dir.generated}" />
<mkdir dir="${dir.build}" />
</target>
<target name="generate" depends="init">
<xjc schema="student.xsd"
target="${dir.generated}">
<produces includes="com/ibm/dw/jaxme/student/*.java" />
</xjc>
</target>
<target name="compile" depends="generate">
<javac srcdir="${dir.generated}"
destdir="${dir.build}">
<classpath refid="classpath.schema-compiler" />
</javac>
<!-- Copy over support files -->
<copy todir="${dir.build}">
<fileset dir="${dir.generated}">
<include name="**/jaxb.properties" />
<include name="**/Configuration.xml" />
</fileset>
</copy>
</target>
<target name="clean">
<delete dir="${dir.generated}" />
<delete dir="${dir.build}" />
</target>
</project>
|
您需要改变
lib.jaxme 属性的值,然后就可以了。该例中只需要输入
ant generate 就可以从模式生成类。您应该把这类工具(以及 Ant)放在手边,因为它使得编译和处理微妙的类路径变得非常简单。
只要充分理解了 JaxMe 处理类生成的方式,就可以很容易的实现与 XML 的相互转化。我将在下一篇文章讨论这个问题,然后再介绍 JaxMe 某些专有的特性,比如使用数据库。在这之前先用映射和 Configuration.xml来打发时间吧,但愿您过得愉快(也需要发一两次火)并真正掌握 JaxMe 的工作原理。在这期间,我将开始撰写下一期的稿子,那时再见吧。
| 名字 | 大小 | 下载方法 |
|---|---|---|
| x-pracdb4-code.zip | 2KB | HTTP |
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
-
下载student.xsd 和 build.xml 的代码。
- 访问
JaxMe网站,进一步了解这种新的 API。
- 请访问
Java Architecture for XML Binding (JAXB)网页。
- 请参阅
Apache Incubator,像 JaxMe 这样新的有创意的项目随时都会出现。
- 请参阅 Brett 关于数据绑定的完整著作,
Java and XML Data Binding
(O'Reilly & Associates)。在
developerWorks
Developer Bookstore还可以找到各种关于 XML 的书籍。
- 如何使用数据绑定轻松实现 XML 文档中存储的数据与 Java 对象的互相转换,请参阅 Daniel Steinberg 撰写的教程“
使用 JAXB 进行数据绑定”(
developerWorks,2003 年 5 月)。
- 要了解使用W3C XML Schema 或者 DTD grammars for XML 文档的代码生成实现 XML 数据绑定的几种方法,请阅读 Dennis Sosnoski 的文章“
数据绑定,第 1 部分:代码生成方法 — JAXB 及其它”(
developerWorks,2003 年 1 月)。
- 获取
Jakarta Commons 包中的文本解析工具。
- 从 Sun 的网站上进一步了解
XML API。
- 要了解如何通过 WebSphere Studio Application Developer V5.1 使用 JAXB 来开发企业应用程序,请阅读 Tilak Mitra 的
这篇文章(
developerWorks,2004 年 2 月)。
- 在
developerWorks
XML和
Java 技术专区可以找到更多数据绑定资源。
- 了解如何才能成为一名
IBM 认证的 XML 及相关技术的开发人员。