作为开发人员,您可能听说过 80-20 规则,在其它领域被称为 Pareto 法则:一种过程或方法能适应所有可能情况的 80%,另外的 20% 则需要根据具体情况来处理。软件开发的必然结果是:对于开发人员而言,有了给定的技术后就能非常容易地完成可能要做的工作的 80%。
当然,软件产品和标准并不总是根据 80-20 规则发展的。特别的,Java XML 的缺陷就是这条规则的一个例外。Java 的编程世界拥有很多的 API -- 一些是自己开发的,一些是由几个大公司开发并被制定为标准的 -- 他们提供了解决特殊 XML 任务的成熟解决方案。作为 XML 普遍性的证明,每个新任务都存在着一种新技术,但如何将它们结合在一起,又如何寻找一种合适的工具去完成必须重复做的任务中的 80% -- 利用 Java 语言的直观映象的基本 XML 树操作?JDOM 正好是用来解决上述问题的一个 XML API。
在许多方面,Java 语言已变成供 XML 选择的一种编程语言。由于 Apache 软件基金会和 IBM alphaWorks 所做的开创性工作,现在已有完整的工具链用于创建,操作,传送文档和对 XML 文档进行语法分析。
但是,虽然许多 Java 开发人员每天都在使用 XML,Sun 却在将 XML 整合进 Java 平台方面落后了。因为在 XML 成为从商家对商家集成到 Web 站点内容流水化等方面的关键技术之前,Java 2 平台就已经非常流行了。Sun 已经使用 JSR 过程使之成为现存 XML API 的鼻祖,这一点已被广泛接受。目前最显著的是加入了 JAXP (用于 XML 语法分析的 Java API),其中包含了三个软件包:
- org.w3c.dom ,W3C 推荐的用于 XML 标准规划文档对象模型的 Java 工具
- org.xml.sax ,用于对 XML 进行语法分析的事件驱动的简单 API
- javax.xml.parsers ,工厂化工具,允许应用程序开发人员获得并配置特殊的语法分析器工具
尽管对于 Java 开发人员而言,有了这些软件包是件好事,但它仅仅代表获得了现有 API 标准的正式许可而已,并没有在提供一流的 Java-XML 互操作性方面取得了巨大飞跃。核心 Java 平台所缺乏的是将 XML 文档作为 Java 对象操作的直观接口。
进入 JDOM。JDOM 是两位著名的 Java 开发人员兼作者,Brett Mclaughlin 和 Jason Hunter 的创作成果, 2000 年初在类似于 Apache 协议的许可下,JDOM 作为一个开放源代码项目正式开始研发,JDOM 作为一个开放源代码项目正式开始了。它已成长为包含来自广泛的 Java 开发人员的投稿、集中反馈及错误修复的系统,并致力于建立一个完整的基于 Java 平台的解决方案,通过 Java 代码来访问、操作并输出 XML 数据。
JDOM 能够替换 org.w3c.dom 软件包来有计划地操作 XML 文档。它并不是一个简单的替代品,实际上 JDOM 和 DOM 能够愉快地并存。另外,尽管它提供的类的封装从配置和运行分析器执行中分担了大量工作,但它不负责根据文本输入来对 XML 进行语法分析。JDOM 建立在现有的 API 的能力之上,正如项目网页所表述的“一个更好的捕鼠器”。
要理解需要备用 API 的原因,就要考虑 W3C DOM 设计的局限性:
- 语言独立。DOM 并不是用人们心目中的 Java 语言设计的。虽然这种方法保留了在不同语言中非常相似的 API,它也使那些习惯 Java 语言的程序员感到更麻烦。例如:Java 语言内建了一种 String 类,而 DOM 则规范定义了自己的 Text 类。
- 严格的层次结构。DOM API 直接沿袭了 XML 规范。在 XML 中,每件东西都是一个结点,因此您能在 DOM 中找到一个几乎每件东西都可以扩展的基于 Node 的接口和返回 Node 的一系列方法。就多态性的观点来讲,它是优秀的,但鉴于如上解释,它在 Java 语言中的应用是困难而且不便的,其中从 Node 向叶类型作显式下拉会导致代码的冗长和难以理解。
- 接口驱动。公共 DOM API 仅由接口组成( Exception 类是一个例外,但恰恰足够了)。w3c 对提供实现并不感兴趣,它只对定义接口(比较有意义)感兴趣。但它也意味着作为 Java 程序员使用 API 在创建 XML 对象时增加了分散程度,因为 w3c 标准大量使用工厂化的类和类似的灵活的但不直接的模式。在某些应用中,XML 文档是仅由语法分析器建立的,而从不会由应用程序级代码建立,这是不相关的。但是,随着 XML 更广泛的使用,并不是所有问题都继续需要由语法分析器来驱动。应用程序的开发人员需要一个更方便的方法有计划地构造 XML 对象。
对于程序员,这些约束意味着庞大(在内存占用和接口大小方面)的和难掌握的 API,学习和使用都很难。相反,JDOM 是作为一种轻量级 API 被制定的,最主要的是它是以 Java 为中心的。它在遵循 DOM 主要规则的基础上除去了上述缺点:
- JDOM 是 Java 平台专用的。只要有可能,API 都使用 Java 语言的内建 String 支持,因此文本值也适用于 String 。它还可利用 Java 2 平台的类集,如 List 和 Iterator ,给程序员提供了一个丰富的并且和 Java 语言类似的环境。
- 没有层次性。在 JDOM 中,XML 元素就是 Element 的实例,XML 属性就是 Attribute 的实例,XML 文档本身就是 Document 的实例。由于在 XML 中所有这些都代表了不同的概念,因此它们总是作为自己的类型被引用,而不是作为一个含糊的“结点”。
- 类驱动。因为 JDOM 对象就是像 Document 、 Element 和 Attribute 这些类的直接实例,因此创建一个新 JDOM 对象就如在 Java 语言中使用 new 操作符一样容易。它还意味着不需要进行工厂化接口配置 -- JDOM 的使用是直截了当的。
JDOM 使用标准的 Java 编码模式。只要有可能,它使用 Java new 操作符而不用复杂的工厂化模式,使对象操作即便对于初学用户也很方便。例如,让我们看一下如何随便使用 JDOM 建立一个简单的 XML 文档。我们将要建立的结构如清单 1 所示。(从 参考资料上可下载关于本文的完整代码)
|
注意:我们将建立 示例文档,在下面的清单 2 到清单 7 中有详细描述。
开始,让我们先创建一个根元素,并将其添加到文档中:
清单 2. 创建一个 Document
Element carElement = new Element("car"); Document myDocument = new Document(carElement); |
这一步创建一个新 org.jdom.Element ,并将其作为 org.jdom.Document myDocument 的根元素。(如果您使用 参考资料中提供的样本代码,请务必导入 org.jdom.* 。)因为一个 XML 文档必须一直有一个唯一的根元素,所以 Document 将 Element 放在它的构造器中。
下一步,添加 vin 属性:
carElement.addAttribute(new Attribute("vin", "123fhg5869705iop90")); |
添加元素也是很简单的。这里我们添加 make 元素:
Element make = new Element("make"); make.addContent("Toyota"); carElement.addContent(make); |
由于 Element 的 addContent 方法返回 Element ,我们也可以这样写:
carElement.addContent(new Element("make").addContent("Toyota")); |
这两个语句完成了相同的工作。有些人认为第一个示例可读性更好,但是如果您一次建立许多元素,您会觉得第二个示例可读性更好。要完成构建文档:
carElement.addContent(new Element("model").addContent("Celica")); carElement.addContent(new Element("year").addContent("1997")); carElement.addContent(new Element("color").addContent("green")); carElement.addContent(new Element("license") .addContent("1ABC234").addAttribute("state", "CA")); |
您会注意到对于 license 元素,我们不但添加了元素的内容,还为其添加了一个属性,表明许可已被发出了这个状态。这是因为 Element 的 addContent 方法总是返回 Element 本身,而不是一个无效的声明。
用同样的方法添加注释部分或其它标准 XML 类型:
carElement.addContent(new Comment("Description of a car")); |
操作文档也是用类似方式。例如,要引用 year 元素,我们使用 Element 的 getChild 方法:
Element yearElement = carElement.getChild("year"); |
该语句实际上将返回第一个元素名为 year 的子 Element 。 如果没有 year 元素,则调用返回一个空值。注意,我们不必回溯来自任何类似于 DOM Node 接口的返回值 -- Element 的子元素就是 Element 。用类似的方式,我们可把 year 元素从文档中除去:
boolean removed = carElement.removeChild("year"); |
这次调用将只除去 year 元素;文档的其余部分保持不变。
到目前为止,我们已经涵盖了文档的生成和操作。要将完成的文档输出至控制台,可使用 JDOM 的 XMLOutputter 类:
try { XMLOutputter outputter = new XMLOutputter(" ", true); outputter.output(myDocument, System.out); } catch (java.io.IOException e) { e.printStackTrace(); } |
XMLOutputter 有几个格式选项。这里我们已指定希望子元素从父元素缩进两个空格,并且希望元素间有空行。 XMLOutputter 可输出到 Writer 或 OutputStream 。为输出到文件,我们可以简单地将输出行简化为:
FileWriter writer = new FileWriter("/some/directory/myFile.xml"); outputter.output(myDocument, writer); writer.close(); |
JDOM 的一个有趣特征是和其它 API 有互操作性。使用 JDOM,不仅能把文档输出到 Stream 或 Reader ,还可将文档作为 SAX Event Stream 或作为 DOM Document 。这种灵活性允许 JDOM 能在多种环境下使用或被添加到已经在使用另一种方法处理 XML 的系统中去。正如我们在后面一个示例中所看到的,它还允许 JDOM 使用其它的还不能识别 JDOM 的数据结构的 XML 工具。
JDOM 的另一个用处是它能够读取并操作现有的 XML 数据。使用 org.jdom.input 中的一个类可以阅读结构很规范的 XML 文件。在这个示例中我们使用 SAXBuilder :
try { SAXBuilder builder = new SAXBuilder(); Document anotherDocument = builder.build(new File("/some/directory/sample.xml")); } catch(JDOMException e) { e.printStackTrace(); } catch(NullPointerException e) { e.printStackTrace(); } |
您可以用清单 2 到清单 7 中显示的方法来操作通过这个过程建立的文档。
JDOM 的另一个实用应用程序将其与 Apache 的 Xalan 产品结合在一起(请参阅 参考资料)。使用上面的汽车示例,我们将为在线汽车经销商建立一个 Web 页面,显示特定汽车的详细信息。首先,假设我们上面建立的文档显示我们准备呈现给用户的汽车的信息。下一步,我们将把这个 JDOM Document 与一个 XSL 样式表结合起来并把 HTML 格式的结果输出到 servlet 的 OutputStream 上以便在用户的浏览器中显示。
在本例中,我们准备使用的 XSL 样式表被称为 car.xsl :
|
现在我们将把 org.jdom.Document 转换为 DOM Document ,并将其与显示我们的 XSL 和 OutputStream 的文件一起提供给 Xalan, OutputStream 是我们从我们假定的使用 servlet(如清单 14 所示)的应用服务器上获取的。
TransformerFactory tFactory = TransformerFactory.newInstance(); // Make the input sources for the XML and XSLT documents org.jdom.output.DOMOutputter outputter = new org.jdom.output.DOMOutputter(); org.w3c.dom.Document domDocument = outputter.output(myDocument); javax.xml.transform.Source xmlSource = new javax.xml.transform.dom.DOMSource(domDocument); StreamSource xsltSource = new StreamSource(new FileInputStream("/some/directory/car.xsl")); // Make the output result for the finished document using // the HTTPResponse OutputStream StreamResult xmlResult = new StreamResult(response.getOutputStream()); // Get a XSLT transformer Transformer transformer = tFactory.newTransformer(xsltSource); // Do the transform transformer.transform(xmlSource, xmlResult); |
在这个示例中,输出是通过 Java servlet 的 HTTPResponse OutputStream 流出。然而,输出流可以象早期的使用 XMLOutputter 的实例一样简单的通过文件流输出。我们使用 DOMOutputter 为 Xalan 生成 XML 源代码。但是我们可以生成相同的输出,方法是使用 XMLOutputter 将我们的 XML 文档作为 String 输出并使其进入 StreamSource 。说到灵活性:JDOM 可将它的结构作为 String 、SAX Event Stream 或 DOM Document 输出。这允许 JDOM 与能把任何这些模型作为输入的工具一起工作。(关于附加功能,请访问 JDOM Web 站点的 contrib 包,在那里您将发现一个基于 JDOM 工具的宝库,可提供基于 JDBC ResultSet 的构建器、XPATH 实现方法和其它更多工具。)
在短短几行代码中,JDOM 启用了许多功能,我们已经在 XML 中分析过并有计划地创建了 XML 文档,操作了那些文档,并使用它们产生 XML 驱动的 Web 页面。
正如此文所提的那样,JDOM 项目已经发布了它的 Beta 6 版本。甚至在 beta 状态下,对于许多真实世界中的实现方法来说,JDOM 已经被证明是稳定的一种了。尽管大部分的 API 已经稳固了,但在一些领域中仍在进行一些会对现有的接口造成潜在影响的工作。因此,在这点上,任何在进行的开发项目都不需要因为害怕一个错误多多的实现方法而回避 JDOM,但是要考虑这样一个事实:某些方法结构或某些语义仍有可能在最终发布和被核心 Java API 所采用之前发生改变。(请参阅 名字里包含了些什么?)
JDOM 紧接着要做的是致力于稳定 API 并对实现方法的各方面性能问题作出评估。其它方面有所进展,但也造成了对一些应用程序开发人员的阻碍,包括支持 DTD 实体和其它不太常见的构造。沿着这条路再进一步就是对 XPATH(它是一种象 XSLT 这样的应用程序所特有的 XML 路径语言)的核心支持以及更多地集成 XML 数据源。
那么,概况地说,JDOM 是否比现有的 XML API 好呢?如果您梦想 Java,那答案可能是“是的”。JDOM 并非意味着将取代您所喜爱的语法分析或 XML 敏感型数据库,但其设计原则有助于为试图掌握 XML 世界的新老 Java 开发人员提供快速的学习途径。