从XML到Java的数据绑定系列之四:从无用的字符到有用的代码段
Brett McLaughlin
Enhydra 策略顾问,Lutris
Technologies
2000 年 10 月
本数据绑定系列的最后一部分(第四部分)完成了绑定类的集合,并且讨论了使用仍在开发的 JSR-031,也叫做 Adelard,将
Java 对象打包成 XML 表示的过程。这部分研究了执行该操作所需的代码,并讨论了此代码与前一部分研究的 Java
类之间的关系。最后,使用实际例子来运行此数据绑定代码的最新部分。
在本系列的上一部分中,我演示了如何取出 XML 文档并将它转换成 Java
表示。这种变换的关键是 XML 文档符合的 XML 模式。模式不仅确保了强制约束。它还允许使用 SchemaMapper 来生成 Java 类;那么 XML
文档就可以解包成那些类其中一个的实例。换句话说,这个系统不仅需要 XML 文档;文档将变成其实例的 Java
类不仅必须已经存在,而且它还必须在系统的类路径中。
打包类
打包 Java 实例时,情况稍有不同。首先,Unmarshaller
类不存储关于在所创建的 Java 实例中使用的 XML 模式的信息。因此,从 XML 文档创建的 Java 类的实例与任何其它 Java
类没有本质区别。最初,这似乎是一个问题。Java 类将实例转回 XML 是否必须采取更多的操作?答案是“是”。然而,这却是好事:从 XML 解包的类完全独立于该
XML 文档。这有以下几个优点:
Java 实例可以解包成不同的 XML 文档。
打包代码不依赖于查找 XML 模式或 XML 文档。
对这个 Java 实例使用反射和其它 Java 方法不会产生意外的字段或方法。
前两点可能是显而易见的,而第三点也许不是。Java
应用程序中正越来越广泛地使用反射,以在运行时处理各种不同类型的对象。通常会检查字段和方法以确定如何使用对象。如果要创建附加方法(如
toXML())或附加字段(如
xmlSchema),类会将这些方法返回到检查它的应用程序。最终结果是其它应用程序代码可能会错误地使用特别用于打包和解包数据的信息。在数据绑定的环境之外,很可能会误用这些方法和字段(或变量)。它们甚至会给出不该披露的网络资源(如
XML 模式)的应用程序信息。
正是由于这些原因,有必要承认将 Java 打包成 XML 比使用原来的 XML
模式进行打包略有难度。但是,因为有了这个难度,生成的 Java 实例才独立于 XML
文档或模式。如果所有这些令您感到困惑,请按以下方式考虑这个问题:可以将已解包的 Java 对象和已打包的 XML
文档返回给其它应用开发者,且不必给出任何特定的用法指令。实际上,他们甚至不知道数据原来是 XML 还是
Java;他们所看到的数据格式只是您提供的格式。
这是一条要记住的重要原则 -- 隔离数据检索方法。那就更容易遵守使数据尽可能保持专用的基本指南。例如,关于如何从 XML
文档中获取数据的信息并不适合分发给其它应用开发者,特别是他们在不同的公司(如在商家对商家应用程序中)。
深入研究
我已经讲述了一些预备概念,现在该研究代码了。首先,需要添加一个入口点,以便将 Java
对象打包成 XML 文档。如同 Unmarshaller 类,其它程序最容易从静态方法中使用打包代码。如果没有选项需要传入 Java
对象,就象这里讨论的情况,这种方式很有效。同时,遵守好的编程惯例、仅内部使用这个静态方法构造对象,以及调用非静态方法也很重要。这个静态方法是打包 Java
对象的“前门”。除了取出对象进行打包,静态方法还应该可以输出 XML 文档。
这里,需要避免一个常见错误,即允许将 XML 文档输出成 String 或
File。这两种输出方法假设将对象打包成某些永久存储,并保存在物理硬盘上。尽管,对象经常通过网络流传递,如传递到另一个应用程序。或者它可以交给 XSLT
处理器,该处理器将 XML 变换成其它形式。这两种情况下,String 或 File 都不能完全解决问题。实际上,应该选取一个较安全的路由,并允许提供用于写入的
OutputStream。这个流可以封装 FileOutputStream,以便写入本地文件、网络连接或者到另一个程序的管道(如刚提到的 XSLT
处理器)。确定了这个自变量后,才可以考虑 Marshaller 类的入口点。
清单 1. 静态入口点
/**
*
* This method is the public
entry point for marshalling an object into
* an XML instance
document.
*
*
* @param obj
Object to convert to XML.
* @param out
OutputStream to write XML to.
* @throws
IOException when errors in output
occur.
*/
public static void marshall(Object obj, OutputStream
out) throws IOException {
Marshaller marshaller = new
Marshaller();
marshaller.writeXMLRepresentation(obj,
out);
}
看上去很简单,是吗?很好。除了文章中的这个和其它代码片段,还可以下载完整的
Marshaller 类,并查看 HTML 格式的类。
一旦内部创建了 Marshaller
类的实例,提供给静态方法的变量就传递到同一方法的动态版本。因为我已经让所提供的流写入结果,所以不需要返回值。建造了前门之后,就该研究房子的其余部分了。
声明 XML 模式无关性
就象我以前提到过的,不能依赖 XML 模式或任何插到
Java 类中的方法来简化从 Java 到 XML 的转换。相反,我们必须从 Java 类中的数据手工创建 XML
文档。但是,这种决定非常重大的副作用是您可能采用任何符合 JavaBean 式样格式的 Java 类来生成 XML 表示。我说 JavaBean
式样是因为不必实现 JavaBean 接口,但对于类,希望它对接口的所有数据具有读方法。例如,如果类有一个名为 name 的字段,则希望有一个返回该值的方法
getName。任何没有象这样的读方法的数据字段都将导致在打包过程中忽略该数据。
强制了这种简单的约束(并且这是标准编程惯例)之后,就可以将任何 Java 对象转换成 XML。这还允许来回转换 XML
数据。XML 文档可以转换成 Java 实例,然后再转换回 XML。但是,有一个告诫:原始 XML 模式声明会丢失!请记住,这就是我们的 Java
打包过程不依赖 XML 模式的副作用。打包得到的 XML 输出完全不知道任何约束 XML 的 XML 模式。再次将 XML 解包成 Java
时,这不会引起问题,因为类路径中只需要包含必须创建其新实例的表示对象的类。您可以回过去看一下第三部分,我在解包过程中没有使用 XML 模式。因此,将 XML
转换成 Java,再转换回 XML
并不会发生问题,只是会丢失模式约束。当然,本系列中的所有代码都是开放源码,因此如果您的应用程序中需要这个功能,欢迎您将此功能添加到应用程序中。
然后,主方法比较简单。它接受一个对象,应该返回该对象的 XML 元素表示。如果对象包含对其它 Java
对象的引用,那么可以使用相同的方法进行递归,并将一个“子”元素添加到顶级元素。这个最终元素被返回到调用方法,并输出到所提供的流。(我将在下一节中讨论此最终方法。)因为
XML 的 JDOM 表示易于操作代码,所以仍使用它。清单 2 中显示了这个核心方法。
清单 2. 将 Java 对象转换成 XML 的核心方法
/**
*
* This is the granular portion
of binding; a Java Object is
* converted into
an XML element (in JDOM form), using recursion for any
*
children.
*
*
* @param obj
Object to get the XML element representation
for.
* @return Element - representation of
Object in XML.
* @throws
IOException when errors occur in
binding.
*/
private Element getXMLRepresentation(Object obj)
throws IOException {
Class objectClass = obj.getClass();
// Get the name of the element for this
object
String objectName =
BindingUtils.initialLowercase(objectClass.getName());
// If this is an "Impl" class, remove that from the
name
int index = -1;
if ((index = objectName.indexOf("Impl")) ! =
-1) {
objectName = objectName.substring(0,
index);
}
Element element = new Element(objectName);
Method[] methods = objectClass.getMethods();
for
(int i=0; i // Only want accessor methods,
but not the getClass() method in Object
Method method =
methods[i];
if ((method.getName().startsWith("get"))
&&
(!method.getName().equals("getClass"))) {
// Get the value for this method
try
{
Object o = method.invoke(obj, new Object[] { });
// For the name, remove the "get" and lower the initial
letter
String propertyName
=
BindingUtils.initialLowercase(method.getName().substring(3));
// Determine if it's primitive by seeing if it's a
java.lang type
if (o.getClass().getName().startsWith("java.lang."))
{
// If it's a primitive, add as an
attribute
element.addAttribute(propertyName, o.toString());
}
else { // ... otherwise, recurse and add new element as
child
element.addContent(getXMLRepresentation(o));
}
} catch (IllegalAccessException e) {
throw new
IOException(e.getMessage()); } catch (InvocationTargetException e)
{
throw new IOException(e.getMessage());
}
}
}
return element;
}
可以看到,代码非常简单。首先,获取 Java 类的名称。这个名称将变成正在构造的 XML
表示的元素名称。BindingUtils 中一个新的实用程序方法用于将此名称转换成以小写字母开头的名称。(标准 XML
名称都以小写字母开头。)在参考资料节中可以下载更新的 BindingUtils 类,以及代码包的其余部分。另外,如果类是 Impl 类,将除去名称的
"Impl" 部分。请记住,在本系列的第三部分中,类就是接口(如 WebServiceConfiguration)和实现(如
WebServiceConfigurationImpl)。此外,这是标准编程惯例,并且该方法将在普通情形中起作用。
一旦元素名称就绪,则必须获取属性。每个属性都应该是字段名称和字段的值。将任何复杂对象(非 Java
原语)转换成嵌套元素。要获取此数据,可以使用反射来获取对象的所有可用方法。您只需要关心读方法(以 "get" 开头);其余可以忽略,包括从
java.lang.Object 继承的 getClass() 方法。然后,(再次)使用反射来调用方法,获取它的值。最后,可以再使用 BindingUtils
类从其方法派生出字段名称。因此,方法 getVersion() 将导致创建一个叫做 "version" 的属性,它的值通过调用 getVersion()
方法产生。
您将注意到以上最后要指出的是字段是 Java 原语(其中类在 java.lang
包中)还是嵌套对象。前一种情况下,属性添加到整个元素。后一种情况下,将发生递归并创建一个子元素。一旦以这种方式处理了每个读方法,生成的 JDOM
就返回到调用程序。一旦展开了递归,其结果就是顶级 Java 对象的完整的 XML 表示,它不能用作 XML 文档的根元素。
完成接触
只需一点努力,您就学会了打包代码。所剩下的就是在输入方法和递归方法之间的缝隙之上建立桥梁。使用
JDOM 输出类 org.jdom.output.XMLOutputter 可以很容易就跨越这个缝隙。这个类使用 JDOM Document 和
OutputStream,输出
XML。当然,我们有一个流可以使用,还可以将从刚讨论过的方法中返回的元素作为文档根元素使用,来创建一个简单的文档。将这个元素和流传递到 XMLOutputter
中并调用 output() 方法来实现这个技巧。清单 3 中显示了这个方法;它由静态 marshall
方法调用,并使用我们刚讨论过的方法。
清单 3. 将各个细节与 output() 方法连接
/**
*
* This will take a Java object
instance, and convert it into an
* XML document, and write that
document to the supplied output stream.
*
*
* @param obj Object to
convert to XML.
* @param out OutputStream to
write XML to.
* @throws IOException when
errors in output occur.
*/
private void
writeXMLRepresentation(Object obj, OutputStream out)
throws IOException
{
// Root Element is the start of recursion
Element root =
getXMLRepresentation(obj);
Document doc = new
Document(root);
// Use 2 space indentation and line
feeds
XMLOutputter outputter = new XMLOutputter(" ",
true);
outputter.output(doc, out);
}
这就完成了。Marshaller
类已完成,可以使用了。当然,如果不将它投入实用,它也不起作用,我们将在下一段中讨论实际使用这个类。
实践出真知
就象任何好的代码一样,您应该放下理论,实际使用此代码。本文的其余部分讨论了在实际情况下,首先使用
Marshaller 类,然后使用整个 org.enhydra.xml.binding 包。所以,让我们将这些类投入实际使用。
请记住,在本系列的第三部分中,测试 Unmarshaller 类时做的第一件事就是编写一个相当简单的类
TestMapper。尽管这个类只能对解包进行基本测试,但它却是开发数据绑定类过程中的关键部分。当然,在任何应用程序中,编码新功能后的第一件事就是针对该功能编写一个非常基本的测试。在将新功能放到一个大应用程序中的过程中(通常是件好玩的事),这通常只是处理隐蔽错误的好方法。而有一个测试类可以适用于每个应用程序类,有时适用于每个类的方法(是的,您没有看错),可以节省您的调试时间。有几种好的结构可以帮助自动执行这些类型的测试:JUnit
是一个很棒的免费测试包、JTest 是一个很好的需付费测试包。请您的公司投资购买一个测试包吧,长期使用后您会发现它物超所值。
在我鼓吹了实际应用的重要性之后,我将讨论这个测试类。加上它,可以测试 Unmarshaller 和 Marshaller
类。是的,我知道这破坏了我刚谈到的规则,但为了使本文的篇幅控制在 20 页以内,我只能这么做。清单 4
中显示了这个类,其中的更新可以帮助测试新的类。
清单 4. 测试 Marshaller
import java.io.File;
import
org.enhydra.xml.binding.Marshaller;
import
org.enhydra.xml.binding.Unmarshaller;
public class TestMapper {
public static void main(String[] args)
{
System.out.println("Starting unmarshalling...");
try
{
System.out.println("\n\n......... Start of Unmarshaller test
............\n\n");
File file = new File("xml/example.xml");
Object o =
Unmarshaller.unmarshall(file.toURL());
System.out.println("Object
class: " + o.getClass().getName());
System.out.println("Casting to
WebServiceConfiguration...");
WebServiceConfiguration config =
(WebServiceConfiguration)o;
System.out.println("Successful
cast.");
System.out.println("Name: " +
config.getName());
System.out.println("Version: " +
config.getVersion());
System.out.println("Port Number: " +
config.getPort().getNumber());
System.out.println("Port Protocol: " +
config.getPort().getProtocol());
System.out.println("\n\n......... End of Unmarshaller test
............\n");
System.out.println("\n\n......... Start of Marshaller test
............\n\n");
Marshaller.marshall(o, System.out);
System.out.println("\n\n......... End of Unmarshaller test
............\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
新的代码以突出显示的字体显示(还有自上一篇文章后添加到测试类的一些有用的调试消息)。新代码的第一段读取第三部分中涵盖的 XML
文档,创建该文档的 Java 表示,并打印出关于已解包数据的信息。然后,将 Java 对象打包回 XML,并将其结果放到系统的
OutputStream,当然输出到屏幕上。运行 TestMapper 程序时,其输出类似于清单 5。
清单 5. 最终结果
$ (/projects/dev/mapper:bmclaugh)> java TestMapper
xml/configuration.xsd
Starting unmarshalling...
......... Start of Unmarshaller test ............
Object class: WebServiceConfigurationImpl
Casting to
WebServiceConfiguration...
Successful cast.
Name: Unsecured Web
Listener
Version: 1.1
Port Number: 80
Port Protocol: http
......... End of Unmarshaller test ............
......... Start of Marshaller test ............
........ End of Unmarshaller test ............
乍看,这里显示的 XML 输出与您本地机器上的 XML
文档(可以从参考资料部分的一个链接中下载)差别很大。但是,仔细观察之后,可以发现两个文档之间只有很少差异。如果忽略元素之间的间隔和缩排,所有属性及其值都与输入文档完全相同。唯一的区别就是
XML 模式引用(和关联名称空间)不见了,正如缺省名称空间声明一样。正如我早先讨论过的,这是有意的,使 Java
对象可以独立于其余数据绑定代码而存在。至于缺省名称空间,必须在 Unmarshaller 创建 Java
对象时将关于该名称空间的某些信息存储到该对象中,以便保存。在 XML
应用程序中,可以选择关闭名称空间处理(使带缺省名称空间的元素等价于不带任何名称空间的元素,因为两者都没有前缀),或者可以修改代码以使它适合您的特殊需要。
在这两种情况下,都可以清楚地看到,有一个从 Java 对象创建 XML 文档的功能性过程。甚至可以插入其它 Java 对象 --
包括不是从 XML 创建的 Java 对象 -- 还可以查看它们的 XML 表示。我们接着在更深的层次上讨论 Marshaller
类,并在更实际的示例中使用它。
讨论 Web 服务
还记得我要讨论的 Web
服务吗?它又回来了。前一部分讨论了启动 Web 服务是多么简单,以及如何使用 XML 配置文档来存储其数据。将该数据解包成 Java 对象允许 Web 服务将
XML 数据作为 Java 变量放到它的方法中。数据绑定使获取 XML 数据的过程变得简单且直接,不必处理 DOM 或者研究 SAX。
现在,让我们看看另一方面:关闭 Web
服务并存储数据。这很平常,服务在一个端口上启动,(例如)还有一个文档根和错误页面,然后它最终有许多数据字段的值改变了。用户管理服务,同时会做一些修改。但是,关闭服务器时不存储数据将使这些更改丢失。要将此数据放回到原来的
XML 文档中很简单,请使用清单 6 中显示的 Marshaller。
清单 6. 启动和停止 Web 服务
import java.io.File;
import
java.io.FileOutputStream;
import java.io.IOException;
import myApp.WebServiceConfiguration;
public class WebService() {
/** File to write configuration data to
*/
private File configurationFile;
/** Configuration variables */
private
WebServiceConfiguration config;
public WebService(String configurationFile) throws
IOException {
this(new File(configurationFile));
}
public WebService(File configurationFile) throws
IOException) {
this.configurationFile =
configurationFile;
}
/**
* Various mutator methods for configuration
data would be included.
* Each would "proxy" through and set the config
object's data.
*/
public void start() throws IOException
{
// Obtain the configuration
config =
Unmarshaller.unmarshal(configurationFile.toURL());
}
public void stop() throws IOException {
// Save
the configuration object
Marshaller.marshal(config, new
FileOutputStream(configurationFile));
}
}
可以看到,我将 Web 服务的初始配置移到 start()
方法。服务的构造器接受从中装入配置数据的文件。然后,构造器将数据保存到同一个文件中。在 stop() 方法中永久保存数据。另外,服务的所有数据都存储在
config(它存储服务使用的基本数据)中,而不必使用多个成员变量(如 portNumber 或 name)。这就是以后要永久保存的对象。除了使编写
start() 和 stop() 方法变得很简单(每个方法只有一行!),这个方法还允许 Web
服务存储其它本来就是“临时的”且无需永久保存的数据。
当然,类也许还包括了这里没有谈到的其它方法。但是,已经讨论的一些方法显示了装入和存储 XML 数据是多么简单,甚至不必知道
XML。那么,看了这个例子之后,还什么要讨论呢?只有一小部分代码更新,然后就完成了。
逐步发展的
API(续)
如果您认为所看到的内容是重复的,或者这个标题是从本系列第三部分中抄过来的,不用担心。正如 JDOM
从第二部分到上一篇文章的不断变化一样,从上一篇文章到本书中,它也不断变化着。实际上,最近发行了 JDOM Beta 5 --
它与前一版本的区别很大。要实现这些改进的功能,数据绑定代码也要不断改变。幸好,从上一篇文章到本文中,代码中的许多更改都不是很重要,而您的老版本仍可以照常运行。但我还是建议您使用参考资料中的链接,获取各种数据绑定类的最新版本。我在本文的最后编辑阶段,已经用
JDOM 的最新版本(Beta 5)对它们进行了测试。所以,如果您手边是老版本的
JDOM,或者此代码的老版本,或者都是老版本,请利用这个机会升级到最新同时也是最棒的版本。
结束语
您已经通读了这四篇全面深入的数据绑定文章。如果您已经开始使用尖括号,并且不常使用空格,不必担心;这可以高级玩意!太好了,您已经开始看到这种方法的强大功能了,并且已经考虑如何在应用程序中使用数据绑定类了。下次您编写配置文件、分析
XML 文档语法或者从 Java 转换成永久存储时,请考虑数据绑定是否可以使您的作业更简单或更有效。
最后请注意:随着本系列即将结束,您不必考虑代码的发展。文章中提到的代码将并入 Enhydra
应用程序服务器结构中。当您阅读本文时,Enhydra 站点上应该有存放此代码的公司 FTP
服务器的链接可供下载使用。它将被不断维护、增强,并且不断反复,直到它可以合并到实际的 Enhydra
应用程序服务器中。所以,请与我们一起使这个开放源码项目变成更实用、更有效、更适合每个人使用的好工具。我们以后还会见面,也许在
Enhydra,也可能是我即将推出的关于 SOAP、JSP 以及许多更有趣话题的文章中。
从无用的字符到有用的代码段
从 XML 到
Java 的数据绑定,第 4 部分
Brett McLaughlin
Enhydra 策略顾问,Lutris Technologies
2000 年 10 月
本数据绑定系列的最后一部分(第四部分)完成了绑定类的集合,并且讨论了使用仍在开发的 JSR-031,也叫做 Adelard,将
Java 对象打包成 XML 表示的过程。这部分研究了执行该操作所需的代码,并讨论了此代码与前一部分研究的 Java
类之间的关系。最后,使用实际例子来运行此数据绑定代码的最新部分。
在本系列的上一部分中,我演示了如何取出 XML 文档并将它转换成 Java 表示。这种变换的关键是 XML 文档符合的 XML
模式。模式不仅确保了强制约束。它还允许使用 SchemaMapper 来生成 Java 类;那么 XML
文档就可以解包成那些类其中一个的实例。换句话说,这个系统不仅需要 XML 文档;文档将变成其实例的 Java
类不仅必须已经存在,而且它还必须在系统的类路径中。
打包类
打包 Java
实例时,情况稍有不同。首先,Unmarshaller 类不存储关于在所创建的 Java 实例中使用的 XML 模式的信息。因此,从
XML 文档创建的 Java 类的实例与任何其它 Java 类没有本质区别。最初,这似乎是一个问题。Java 类将实例转回 XML
是否必须采取更多的操作?答案是“是”。然而,这却是好事:从 XML 解包的类完全独立于该 XML 文档。这有以下几个优点:
- Java 实例可以解包成不同的 XML 文档。
- 打包代码不依赖于查找 XML 模式或 XML 文档。
- 对这个 Java 实例使用反射和其它 Java 方法不会产生意外的字段或方法。
前两点可能是显而易见的,而第三点也许不是。Java
应用程序中正越来越广泛地使用反射,以在运行时处理各种不同类型的对象。通常会检查字段和方法以确定如何使用对象。如果要创建附加方法(如
toXML())或附加字段(如
xmlSchema),类会将这些方法返回到检查它的应用程序。最终结果是其它应用程序代码可能会错误地使用特别用于打包和解包数据的信息。在数据绑定的环境之外,很可能会误用这些方法和字段(或变量)。它们甚至会给出不该披露的网络资源(如
XML 模式)的应用程序信息。
正是由于这些原因,有必要承认将 Java 打包成 XML 比使用原来的 XML 模式进行打包略有难度。但是,因为有了这个难度,生成的 Java
实例才独立于 XML 文档或模式。如果所有这些令您感到困惑,请按以下方式考虑这个问题:可以将已解包的 Java 对象和已打包的 XML
文档返回给其它应用开发者,且不必给出任何特定的用法指令。实际上,他们甚至不知道数据原来是 XML 还是 Java;他们所看到的数据格式只是您提供的格式。
这是一条要记住的重要原则 -- 隔离数据检索方法。那就更容易遵守使数据尽可能保持专用的基本指南。例如,关于如何从 XML
文档中获取数据的信息并不适合分发给其它应用开发者,特别是他们在不同的公司(如在商家对商家应用程序中)。
深入研究
我已经讲述了一些预备概念,现在该研究代码了。首先,需要添加一个入口点,以便将
Java 对象打包成 XML 文档。如同 Unmarshaller 类,其它程序最容易从静态方法中使用打包代码。如果没有选项需要传入
Java 对象,就象这里讨论的情况,这种方式很有效。同时,遵守好的编程惯例、仅内部使用这个静态方法构造对象,以及调用非静态方法也很重要。这个静态方法是打包
Java 对象的“前门”。除了取出对象进行打包,静态方法还应该可以输出 XML 文档。
这里,需要避免一个常见错误,即允许将 XML 文档输出成 String 或
File。这两种输出方法假设将对象打包成某些永久存储,并保存在物理硬盘上。尽管,对象经常通过网络流传递,如传递到另一个应用程序。或者它可以交给
XSLT 处理器,该处理器将 XML 变换成其它形式。这两种情况下,String 或 File
都不能完全解决问题。实际上,应该选取一个较安全的路由,并允许提供用于写入的 OutputStream。这个流可以封装
FileOutputStream,以便写入本地文件、网络连接或者到另一个程序的管道(如刚提到的 XSLT
处理器)。确定了这个自变量后,才可以考虑 Marshaller 类的入口点。
清单 1. 静态入口点
/**
*
* This method is the public entry point for marshalling an object into
* an XML instance document.
*
*
* @param obj Object to convert to XML.
* @param out OutputStream to write XML to.
* @throws IOException when errors in output occur.
*/
public static void marshall(Object obj, OutputStream out) throws IOException {
Marshaller marshaller = new Marshaller();
marshaller.writeXMLRepresentation(obj, out);
}
|
看上去很简单,是吗?很好。除了文章中的这个和其它代码片段,还可以下载完整的 Marshaller
类,并查看 HTML 格式的类。
一旦内部创建了 Marshaller
类的实例,提供给静态方法的变量就传递到同一方法的动态版本。因为我已经让所提供的流写入结果,所以不需要返回值。建造了前门之后,就该研究房子的其余部分了。
声明 XML 模式无关性
就象我以前提到过的,不能依赖 XML 模式或任何插到
Java 类中的方法来简化从 Java 到 XML 的转换。相反,我们必须从 Java 类中的数据手工创建 XML
文档。但是,这种决定非常重大的副作用是您可能采用任何符合 JavaBean 式样格式的 Java 类来生成 XML 表示。我说
JavaBean 式样是因为不必实现 JavaBean 接口,但对于类,希望它对接口的所有数据具有读方法。例如,如果类有一个名为
name 的字段,则希望有一个返回该值的方法
getName。任何没有象这样的读方法的数据字段都将导致在打包过程中忽略该数据。
强制了这种简单的约束(并且这是标准编程惯例)之后,就可以将任何 Java 对象转换成 XML。这还允许来回转换 XML 数据。XML 文档可以转换成
Java 实例,然后再转换回 XML。但是,有一个告诫:原始 XML 模式声明会丢失!请记住,这就是我们的 Java 打包过程不依赖 XML
模式的副作用。打包得到的 XML 输出完全不知道任何约束 XML 的 XML 模式。再次将 XML 解包成 Java
时,这不会引起问题,因为类路径中只需要包含必须创建其新实例的表示对象的类。您可以回过去看一下第三部分,我在解包过程中没有使用 XML 模式。因此,将 XML
转换成 Java,再转换回 XML
并不会发生问题,只是会丢失模式约束。当然,本系列中的所有代码都是开放源码,因此如果您的应用程序中需要这个功能,欢迎您将此功能添加到应用程序中。
然后,主方法比较简单。它接受一个对象,应该返回该对象的 XML 元素表示。如果对象包含对其它 Java
对象的引用,那么可以使用相同的方法进行递归,并将一个“子”元素添加到顶级元素。这个最终元素被返回到调用方法,并输出到所提供的流。(我将在下一节中讨论此最终方法。)因为
XML 的 JDOM 表示易于操作代码,所以仍使用它。清单 2 中显示了这个核心方法。
清单 2. 将 Java 对象转换成 XML 的核心方法
/**
*
* This is the granular portion of binding; a Java Object is
* converted into an XML element (in JDOM form), using recursion for any
* children.
*
*
* @param obj Object to get the XML element representation for.
* @return Element - representation of Object in XML.
* @throws IOException when errors occur in binding.
*/
private Element getXMLRepresentation(Object obj) throws IOException {
Class objectClass = obj.getClass();
// Get the name of the element for this object
String objectName = BindingUtils.initialLowercase(objectClass.getName());
// If this is an "Impl" class, remove that from the name
int index = -1;
if ((index = objectName.indexOf("Impl")) != -1) {
objectName = objectName.substring(0, index);
}
Element element = new Element(objectName);
Method[] methods = objectClass.getMethods();
for (int i=0; i
|
可以看到,代码非常简单。首先,获取 Java 类的名称。这个名称将变成正在构造的 XML
表示的元素名称。BindingUtils 中一个新的实用程序方法用于将此名称转换成以小写字母开头的名称。(标准 XML
名称都以小写字母开头。)在参考资料节中可以下载更新的 BindingUtils
类,以及代码包的其余部分。另外,如果类是 Impl 类,将除去名称的 "Impl" 部分。请记住,在本系列的第三部分中,类就是接口(如
WebServiceConfiguration)和实现(如
WebServiceConfigurationImpl)。此外,这是标准编程惯例,并且该方法将在普通情形中起作用。
一旦元素名称就绪,则必须获取属性。每个属性都应该是字段名称和字段的值。将任何复杂对象(非 Java
原语)转换成嵌套元素。要获取此数据,可以使用反射来获取对象的所有可用方法。您只需要关心读方法(以 "get" 开头);其余可以忽略,包括从
java.lang.Object 继承的 getClass()
方法。然后,(再次)使用反射来调用方法,获取它的值。最后,可以再使用 BindingUtils 类从其方法派生出字段名称。因此,方法
getVersion() 将导致创建一个叫做 "version" 的属性,它的值通过调用
getVersion() 方法产生。
您将注意到以上最后要指出的是字段是 Java 原语(其中类在 java.lang
包中)还是嵌套对象。前一种情况下,属性添加到整个元素。后一种情况下,将发生递归并创建一个子元素。一旦以这种方式处理了每个读方法,生成的 JDOM
就返回到调用程序。一旦展开了递归,其结果就是顶级 Java 对象的完整的 XML 表示,它不能用作 XML 文档的根元素。
完成接触
只需一点努力,您就学会了打包代码。所剩下的就是在输入方法和递归方法之间的缝隙之上建立桥梁。使用
JDOM 输出类 org.jdom.output.XMLOutputter 可以很容易就跨越这个缝隙。这个类使用 JDOM
Document 和 OutputStream,输出
XML。当然,我们有一个流可以使用,还可以将从刚讨论过的方法中返回的元素作为文档根元素使用,来创建一个简单的文档。将这个元素和流传递到
XMLOutputter 中并调用 output() 方法来实现这个技巧。清单 3
中显示了这个方法;它由静态 marshall 方法调用,并使用我们刚讨论过的方法。
清单 3. 将各个细节与 output() 方法连接
/**
*
* This will take a Java object instance, and convert it into an
* XML document, and write that document to the supplied output stream.
*
*
* @param obj Object to convert to XML.
* @param out OutputStream to write XML to.
* @throws IOException when errors in output occur.
*/
private void writeXMLRepresentation(Object obj, OutputStream out)
throws IOException {
// Root Element is the start of recursion
Element root = getXMLRepresentation(obj);
Document doc = new Document(root);
// Use 2 space indentation and line feeds
XMLOutputter outputter = new XMLOutputter(" ", true);
outputter.output(doc, out);
}
|
这就完成了。Marshaller
类已完成,可以使用了。当然,如果不将它投入实用,它也不起作用,我们将在下一段中讨论实际使用这个类。
实践出真知
就象任何好的代码一样,您应该放下理论,实际使用此代码。本文的其余部分讨论了在实际情况下,首先使用
Marshaller 类,然后使用整个 org.enhydra.xml.binding
包。所以,让我们将这些类投入实际使用。
请记住,在本系列的第三部分中,测试 Unmarshaller 类时做的第一件事就是编写一个相当简单的类
TestMapper。尽管这个类只能对解包进行基本测试,但它却是开发数据绑定类过程中的关键部分。当然,在任何应用程序中,编码新功能后的第一件事就是针对该功能编写一个非常基本的测试。在将新功能放到一个大应用程序中的过程中(通常是件好玩的事),这通常只是处理隐蔽错误的好方法。而有一个测试类可以适用于每个应用程序类,有时适用于每个类的方法(是的,您没有看错),可以节省您的调试时间。有几种好的结构可以帮助自动执行这些类型的测试:JUnit
是一个很棒的免费测试包、JTest 是一个很好的需付费测试包。请您的公司投资购买一个测试包吧,长期使用后您会发现它物超所值。
在我鼓吹了实际应用的重要性之后,我将讨论这个测试类。加上它,可以测试 Unmarshaller 和
Marshaller 类。是的,我知道这破坏了我刚谈到的规则,但为了使本文的篇幅控制在 20 页以内,我只能这么做。清单 4
中显示了这个类,其中的更新可以帮助测试新的类。
清单 4. 测试 Marshaller
import java.io.File;
import org.enhydra.xml.binding.Marshaller;
import org.enhydra.xml.binding.Unmarshaller;
public class TestMapper {
public static void main(String[] args) {
System.out.println("Starting unmarshalling...");
try {
System.out.println("\n\n......... Start of Unmarshaller test ............\n\n");
File file = new File("xml/example.xml");
Object o = Unmarshaller.unmarshall(file.toURL());
System.out.println("Object class: " + o.getClass().getName());
System.out.println("Casting to WebServiceConfiguration...");
WebServiceConfiguration config = (WebServiceConfiguration)o;
System.out.println("Successful cast.");
System.out.println("Name: " + config.getName());
System.out.println("Version: " + config.getVersion());
System.out.println("Port Number: " + config.getPort().getNumber());
System.out.println("Port Protocol: " + config.getPort().getProtocol());
System.out.println("\n\n......... End of Unmarshaller test ............\n");
System.out.println("\n\n......... Start of Marshaller test ............\n\n");
Marshaller.marshall(o, System.out);
System.out.println("\n\n......... End of Unmarshaller test ............\n");
} catch (Exception e) { e.printStackTrace();
}
}
}
|
新的代码以突出显示的字体显示(还有自上一篇文章后添加到测试类的一些有用的调试消息)。新代码的第一段读取第三部分中涵盖的 XML 文档,创建该文档的
Java 表示,并打印出关于已解包数据的信息。然后,将 Java 对象打包回 XML,并将其结果放到系统的
OutputStream,当然输出到屏幕上。运行 TestMapper 程序时,其输出类似于清单
5。
清单 5. 最终结果
$ (/projects/dev/mapper:bmclaugh)> java TestMapper xml/configuration.xsd
Starting unmarshalling...
......... Start of Unmarshaller test ............
Object class: WebServiceConfigurationImpl
Casting to WebServiceConfiguration...
Successful cast.
Name: Unsecured Web Listener
Version: 1.1
Port Number: 80
Port Protocol: http
......... End of Unmarshaller test ............
......... Start of Marshaller test ............
......... End of Unmarshaller test ............
|
乍看,这里显示的 XML 输出与您本地机器上的 XML 文档(可以从参考资料部分的一个链接中下载)差别很大。但是,仔细观察之后,可以发现两个文档之间只有很少差异。如果忽略元素之间的间隔和缩排,所有属性及其值都与输入文档完全相同。唯一的区别就是
XML 模式引用(和关联名称空间)不见了,正如缺省名称空间声明一样。正如我早先讨论过的,这是有意的,使 Java
对象可以独立于其余数据绑定代码而存在。至于缺省名称空间,必须在 Unmarshaller 创建 Java
对象时将关于该名称空间的某些信息存储到该对象中,以便保存。在 XML
应用程序中,可以选择关闭名称空间处理(使带缺省名称空间的元素等价于不带任何名称空间的元素,因为两者都没有前缀),或者可以修改代码以使它适合您的特殊需要。
在这两种情况下,都可以清楚地看到,有一个从 Java 对象创建 XML 文档的功能性过程。甚至可以插入其它 Java 对象 -- 包括不是从 XML
创建的 Java 对象 -- 还可以查看它们的 XML 表示。我们接着在更深的层次上讨论 Marshaller
类,并在更实际的示例中使用它。
讨论 Web 服务
还记得我要讨论的 Web 服务吗?它又回来了。前一部分讨论了启动
Web 服务是多么简单,以及如何使用 XML 配置文档来存储其数据。将该数据解包成 Java 对象允许 Web 服务将 XML 数据作为 Java
变量放到它的方法中。数据绑定使获取 XML 数据的过程变得简单且直接,不必处理 DOM 或者研究 SAX。
现在,让我们看看另一方面:关闭 Web
服务并存储数据。这很平常,服务在一个端口上启动,(例如)还有一个文档根和错误页面,然后它最终有许多数据字段的值改变了。用户管理服务,同时会做一些修改。但是,关闭服务器时不存储数据将使这些更改丢失。要将此数据放回到原来的
XML 文档中很简单,请使用清单 6 中显示的 Marshaller。
清单 6. 启动和停止 Web 服务
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import myApp.WebServiceConfiguration;
public class WebService() {
/** File to write configuration data to */
private File configurationFile;
/** Configuration variables */
private WebServiceConfiguration config;
public WebService(String configurationFile) throws IOException {
this(new File(configurationFile));
}
public WebService(File configurationFile) throws IOException) {
this.configurationFile = configurationFile;
}
/**
* Various mutator methods for configuration data would be included.
* Each would "proxy" through and set the config object's data.
*/
public void start() throws IOException {
// Obtain the configuration
config = Unmarshaller.unmarshal(configurationFile.toURL());
}
public void stop() throws IOException {
// Save the configuration object
Marshaller.marshal(config, new FileOutputStream(configurationFile));
}
}
|
可以看到,我将 Web 服务的初始配置移到 start()
方法。服务的构造器接受从中装入配置数据的文件。然后,构造器将数据保存到同一个文件中。在 stop()
方法中永久保存数据。另外,服务的所有数据都存储在 config(它存储服务使用的基本数据)中,而不必使用多个成员变量(如
portNumber 或 name)。这就是以后要永久保存的对象。除了使编写
start() 和 stop() 方法变得很简单(每个方法只有一行!),这个方法还允许 Web
服务存储其它本来就是“临时的”且无需永久保存的数据。
当然,类也许还包括了这里没有谈到的其它方法。但是,已经讨论的一些方法显示了装入和存储 XML 数据是多么简单,甚至不必知道
XML。那么,看了这个例子之后,还什么要讨论呢?只有一小部分代码更新,然后就完成了。
逐步发展的
API(续)
如果您认为所看到的内容是重复的,或者这个标题是从本系列第三部分中抄过来的,不用担心。正如 JDOM
从第二部分到上一篇文章的不断变化一样,从上一篇文章到本书中,它也不断变化着。实际上,最近发行了 JDOM Beta 5 --
它与前一版本的区别很大。要实现这些改进的功能,数据绑定代码也要不断改变。幸好,从上一篇文章到本文中,代码中的许多更改都不是很重要,而您的老版本仍可以照常运行。但我还是建议您使用参考资料中的链接,获取各种数据绑定类的最新版本。我在本文的最后编辑阶段,已经用 JDOM 的最新版本(Beta
5)对它们进行了测试。所以,如果您手边是老版本的 JDOM,或者此代码的老版本,或者都是老版本,请利用这个机会升级到最新同时也是最棒的版本。
结束语
您已经通读了这四篇全面深入的数据绑定文章。如果您已经开始使用尖括号,并且不常使用空格,不必担心;这可以高级玩意!太好了,您已经开始看到这种方法的强大功能了,并且已经考虑如何在应用程序中使用数据绑定类了。下次您编写配置文件、分析
XML 文档语法或者从 Java 转换成永久存储时,请考虑数据绑定是否可以使您的作业更简单或更有效。
最后请注意:随着本系列即将结束,您不必考虑代码的发展。文章中提到的代码将并入 Enhydra 应用程序服务器结构中。当您阅读本文时,Enhydra
站点上应该有存放此代码的公司 FTP 服务器的链接可供下载使用。它将被不断维护、增强,并且不断反复,直到它可以合并到实际的 Enhydra
应用程序服务器中。所以,请与我们一起使这个开放源码项目变成更实用、更有效、更适合每个人使用的好工具。我们以后还会见面,也许在
Enhydra,也可能是我即将推出的关于 SOAP、JSP 以及许多更有趣话题的文章中。
参考资料
不要只阅读上一篇文章;请通读本系列的前几篇文章以了解所有内容:
第一部分比较了数据绑定和
Java 应用程序中其它处理 XML 数据的方法,分析了设计决策,还定义了示例 Web 服务配置文档的 XML 模式。
第二部分详细说明了如何从
XML 模式生成接口和实现。
第三部分研究了如何取得
XML 文档 -- 符合第二部分中创建的 XML 模式 -- 并从这个可以在应用程序中使用的文档创建 Java 实例。
下载 zip
文件,它包含本文中示例的代码,包括 Marshaller 类和老版本类的更新版本。
自己深沉 API:从 SAX (XML 的简单 API(The Simple
API for XML (SAX)) 开始,这是一个连续读取 Java 中 XML 文档的回调驱动的 API。
从 SAX 移到研究 DOM(文档对象模型 (Document Object
Model)),其中 XML 以类树形结构存储和操作。
比这些更高级的 JDOM,这是在示例中使用的 API,提供了对 XML 数据的
Java 优化访问。
所有这一切的起点是什么?
JSR-031:数据绑定 (JSR-031: Data Binding),这是 Sun 公司的数据绑定规格请求。请现在就阅读它!
- 有没有集中了这一切的软件吗?请看 Enhydra,这是一个开放源码
Java 应用程序服务器。
想要再学点东西吗?请学习 Apache
Xerces,它是一个开放源码 XML 语法分析器。
如果这些文章还不够,请订阅由 Brett 撰写、O'Reilly 出版的关于 Java 和 XML 以及它们之间一切的 Java 和 XML (Java and XML)。
Java 语言开发人员,请加入 developerWorks XML
工具和 API 新闻组。
要查找从 XML 对象执行数据绑定的商业工具吗?目前,这类工具基于专有技术;JSR-031 API (aka Adelard)
仍是一个私有工作草案,因此还没有商业产品可以使用它。请查看 KaanBaan,它在“XML
事务服务器”中执行动态数据绑定,还请查看 Breeze XML
Studio。
需要关于 XML 的更多基本介绍吗?请尝试 developerWorks 介绍 XML 教程和提供的其它教程,它们涵盖了大多数基本主题。
关于作者
Brett McLaughlin (brett@newInstance.com) 是 Lutris
Technologies 的 Enhydra 策略顾问,他致力于研究分布式系统体系结构。Brett 是 Java 和 XML (O'Reilly)
的作者。他还参与了如 Java servlet、Enterprise JavaBeans 技术、XML 和商家对商家应用程序等技术的研究。最近,Brett 与
Jason Hunter 一起创立了 JDOM 项目,该项目为在 Java 应用程序中操纵 XML 提供了一个简单的 API。他还是 Apache
Cocoon 项目、EJBoss EJB 服务器的开发人员,并且是 Apache Turbine 项目的共同创始人。
浏览:从XML到Java的数据绑定系列之一
从XML到Java的数据绑定系列之二
从XML到Java的数据绑定系列之三
如果您希望与本文章的作者或其所在机构,进一步交流,请联系:畅享网 姜小姐
jill.jiang@amteam.org | 021-51096826-112 |
在线联系