使用 XML 可以实现各种有趣的功能,但是如果无法将其持久化保存到文件中,那么一切都将是徒劳而已。Brett McLaughlin 将讨论实现 XML 持久化存储的各种不同的技巧,并分别比较其优点和缺点。
XML 是一种伟大的数据格式 — 显而易见,整个 IBM developerWorks 专区都在专注于研究这个主题。2007 年,关于 XML 的讨论多半是 Web 服务,或者 XML 和 Java™ 对象之间的转换,或者读取 XML 配置文件,或者甚至是使用 XML 格式的数据库代替关系或面向对象的数据库。
目前,我们还没有听到有人在谈论如何将所使用的内存表示 — DOM、JDOM 等等 — 中的 XML 保存到静态文件中去,并在其中填满尖括号和引号。坦白的说,获取 XML 并将其写入文件算不上激动人心 — 不过这却是有必要的。试问,编程领域若永远都不能将 XML 持久化存储到文件中会是怎样一番情境?您可以在内存中创建 XML 文档,并且甚至可以将其发送给应用程序中的其他组件(或者其他应用程序的组件);但是却无法存储这些 XML。您可以使用 XML 存储配置数据,同时编写各种工具来读取该数据,但是实际上却无法存储配置文件本身。您甚至还可以读取 SOAP 信封的内容 — 但是却无法将这些内容存储在磁盘上,以供应用程序离线时使用。
显然,将 XML 写入文件非常重要。事实上,如果只是想将数据停留在内存中并且不需要担心数据的存储方式,您可以想象得到不需要存储 XML 的编程世界是什么样子,毫无疑问这在如今的编程领域中是不可能的。
因此,问题十分简单:如何将 XML 持久化存储到文件中去?我假定本文的读者需要自己处理这一任务。换句话说,如果您在编程中从未涉及到持久化存储 XML,那么本文将使您受益匪浅。(但是,了解如何执行这些任务会更利于对文章的理解)对于那些确实关注持久性存储的人,我总结了三种相当常用的主流方法:
- 使用 DOM 和 JDOM 之类的 API 将 XML 数据结构直接写入文件
- 使用 Transformation API for XML (TrAX) 和标识转换(identity transformation)持久化存储您的 XML
- 使用 JAXB 之类的较高级别的 API 处理持久化存储
![]() ![]() |
![]()
|
如果使用一个或多个 API 读取 XML,那么很明显的一个方法就是使用与之相同的 API 将 XML 写入文件。比如说,如果您使用 JDOM API 和一个 JDOM Document
对象操作 XML,那么可以编写以下代码:
XMLOutputter outputter = new XMLOutputter(); outputter.setFormat(Format.getPrettyFormat()); outputter.output(myDocument, new FileWriter("outputFile.xml")); |
与此类似,在 DOM Level 3 中可以使用新的 Load 和 Save API:
DOMWriter writer = new org.apache.xml.serialize.XMLSerializer(); writer.setNewLine("/r/n"); writer.setEncoding("UTF-8"); writer.writeNode(new FileOutputStream(new File("outputFile.xml")), myDocument); |
注意,使用新 DOM API 的方法多种多样,其中有一些具有较低的供应商独立性。上面的示例代码中含有一个特定于 Xerces 的类,但是其他方法不会像它一样与某个特定的供应商类紧密绑定在一起。从学习的角度来说,那些方法都不够直观,因此我保留了特定于供应商的代码。
这种方法的优势是可以相当直接地与您的 API 进行交互,从而实现良好和全面的控制。您可以设置新行,您可以处理首行缩进,您可以控制输出文件的各个方面。此外,还可以尽可能拉近您与文件之间的距离;即没有包装器 API 也没有间接层,您可以直接编写 XML。如果您对 JDOM 和 DOM 比较熟悉,那么它们将是输出 XML 的不二选择。
任何方法有优点就必然有缺点。虽然您可以全面地控制输出的各种细节,但是如果输出配置不当则会导致诸多混乱的问题。换行错误,编写错误和 I/O 错误都是这种方法产生的一些常用问题。除此之外,您还工作于一个非常低的层次,并没有大量的辅助工具(JDOM 在 Format.getPrettyFormat()
和 Format.getCompactFormat()
方法中提供了一些:而 DOM 几乎是一毛不拔)。这意味着您必须要理解编码、输出格式、缩进格式,以及对输出有影响的任何内容。
![]() ![]() |
![]()
|
另一个流行的选择是使用 TrAX 和标识转换。TrAX 是 Transformation API for XML 的缩写,它现在是 JAXP 的一部分,而 Java 平台的每一个发行版中都含有 JAXP(除了 Micro Edition)。TrAX 允许您使用 XSL 样式表对 XML 进行转换。由于 XML 经常需要结合 SAX 和 DOM 一起使用,因此 TrAX 可以接收 SAX 事件和 DOM Document
作为输入,并能够轻易地产生输出文件。此外,TrAX 还可以轻松地对这些格式进行相互转换。比如说,您可以使用以 DOM 表示的 XML 文档作为输入,并对它进行转换,然后再将输出发送到文件中。或者您也可以读取文件中的内容,并对它进行转换操作,然后再将结果文档存储到 DOM Document
中。
这种方法的另外一个作用是,您可以使用一个不含任何文档操作的样式表,并使用某种格式作为输入,然后将这种格式输出为任意其他的格式。使用不具转换功能的样式表 — 实际上指不执行任何操作但回转所接收的输入内容的样式表 — 称作 标识转换(identity transformation)。因此您可以从文件中获得文档,然后应用标识转换,最终在 DOM Document
中生成相同的 XML。如果您采用相反的方式 — 从 DOM 到文件 — 那么实际上可以实现持久化存储 XML。这种方法类似于以下过程:
Source domSource = new DOMSource(myDOMDocument); Result fileResult = new StreamResult(new File("outputFile.xml")); TransformerFactory factory = TransformerFactory.newInstance(); Transformer transformer = factory.newTransformer(); transformer.transform(domSource, fileResult); |
此处,DOM 文档中的 XML 最终转换为了一个 outputFile.xml 文件。
TrAX 最大的优点就是易于使用。拥有 Java 平台访问权的任何人都可以使用它,并且不需要对 SAX 或 DOM 有深入的了解。因此,这对于只有基本 XML 编程技能的开发人员来说是一个极具吸引力的选择。此外,不熟悉 SAX 或 DOM 的初级程序员也可以使用 TrAX — 只需要了解 10 到 20 行函数代码 — 快速将 XML 持久化存储到文件中,或者甚至是 DOM Document
s 和 SAX 事件中。
使用 TrAX 的最大缺点是:虽然可以很容易执行标识转换,但是处理输出细节却需要很高的技巧。换行、编码、空格和缩进 — 所有这些都是 TrAX 提供的配置选项,但是它并不像使用 DOM 或 JDOM 直接配置那样简单。在大多数情况下,TrAX 为普通任务所提供的易用性常常伴随着较低的灵活性,至少不能开箱即用。
注意:在输出方面,使用 TrAX 和标记转换几乎可以实现 JDOM 或 DOM 可以完成的所有任务;只是不够简单或直观而已。您只需要了解一些 XSLT 和 TrAX API 的知识,这两者与所执行的实际输出任务并没有密切的联系。
![]() ![]() |
![]()
|
将 XML 转换为静态格式的另一种方法 — 特别是您希望这种格式是位于磁盘上的文件 — 是使用 JAXB 之类的数据绑定 API。虽然通常人们不会考虑使用数据绑定来实现持久化存储,但是它可以有效地实现:读取内存中表示的 XML 文档并将其写入文件。
我没有太多时间详细介绍数据绑定的概念(您可以在 developerWorks 网站上阅读一些这方面的文章);下面这段简短的代码使用了 JAXB 方式的数据绑定实现持久性存储:
FileOutputStream stream = new FileOutputStream("outputFile.xml"); Marshaller marshaller = myJaxbContext.createMarshaller(); marshaller.marshal(myJavaObject, stream); |
您可以设置一些选项,比如说输出文件的编写,所有设置都在 Marshaller
对象中。事实上,JAXB 在设置输出属性方面的灵活性与前面两种方法是不相上下的。
JAXB 的最大优点就是:它具有极大的易用性,特别是对于一些简单的任务。同时,虽然人们仍然认为 SAX 和 DOM 是主流方法(至少在普通 Java 编程领域是如此),但是 JAXB 对于使用 Java 语言的任何人来说都是家常便饭。这意味着我们可以找到更多 JAXB 方面的文章和教程(2007 发布的一篇调查文章将证实这一点)。此外,对 JAXB 的支持也要优于 DOM 和 SAX。SAX 和 DOM 是 Java 平台标准版本的一部分,而 JAXB 在很大程度上是由 Sun Microsystems, Inc. 发明的。因此,JAXB 的支持稍高一筹也不足为奇了。
此外,使用 JAXB 基本不需要掌握任何 XML 知识。您可以操作普通 Java 对象 — 不是特定于 XML 的对象,如 DOM 的 Node
或 Text
接口 — 并将这些对象直接表示为 XML。这意味着较低的入门门槛,并且任何人都希望可以在很短的时间内掌握它,尤其是当老板在办公室对着您颐指气使的时候。
有其利必有其弊。JAXB 的不足之外在于我们不需要过多了解 XML 便可以使用它。这看上去似乎是我刚刚提到的优点,但是它同时也潜藏着缺点。对 XML 的了解越少,要合理使用 JAXB 就愈加显得困难。您可以轻松地生成一个 XML 文件,但是却会发现这个文件的格式并不可用,或者它只含有一部分需要持久化存储的对象,或者其中的对象与您所编制的对象并不相同。
所有这些常常会导致开发人员将 JAXB 放在一旁,或者大量学习 XML、SAX 和 DOM 方面的知识。这样一来,许多开发人员都会继续使用 SAX 和 DOM 实现持久性存储,而使用 JAXB 只是为实现其最简单的功能:在 XML 和 Java 对象之间相互转换。
![]() ![]() |
![]()
|
我特意将最后一种选择留给大家思考:将 XML 作为一系列比特、节点和字符串直接写入 FileOutputStream
或 FileWriter
。毫无疑问,这种方法可以将 XML 写入文件,并且这种方法的采用也相当多;但是在本例中,持久化存储已有 XML 数据并没有从非 XML 格式的数据中创建 XML 那么频繁。您可以认出这种代码,它们通常类似于以下形式:
String xmlString = setupXMLBuffer( new StringBuffer("<firstName>") .append(customer.firstName) .append("</firstName>") .append("<lastName>") .append(customer.lastName) .append("</lastName>") // etc... .toString() ); bufferedWriter.write(xmlString); // other file I/O code |
这段代码并没有任何错误;我们只是将数据持久化存储于 XML 中,所有操作都在一步之内完成。因此,关于如何持久化存储数据,以及哪种方式是最佳的,这些问题都无关紧要。写入数据并将其存入 XML 的操作是无法分开的,因此再多的讨论也无济于事。
![]() ![]() |
![]()
|
![]() |
|