看一下使用开放源代码 Castor 项目的 Java 的 XML 数据绑定 Dennis M. Sosnoski(dms@sosnoski.com) 总裁,Sosnoski Software Solutions, Inc. 2002 年 7 月
对于主要关心文档的数据内容的应用程序来说,Java 的 XML 数据绑定是 XML 文档模型的强大替代方案。在本文中,企业 Java 专家 Dennis Sosnoski 介绍了数据绑定并讨论了什么使它如此吸引人。然后他向读者展示了如何使用 Java 数据绑定的开放源代码 Castor 框架处理日益复杂的文档。如果您的应用程序关心 XML 的数据更甚于关心 XML 文档本身,您可能希望找出这个处理 Java 中 XML 的容易而又高效的方法。 大多数处理应用程序中 XML 文档的方法都是把重点放在 XML 上:从 XML 的角度处理文档,并按照 XML 元素、属性和字符数据内容编程。如果您的应用程序主要关心文档的 XML 结构,这种方法是挺不错的。对于关心文档中包含的数据更甚于关心文档本身的许多应用程序来说,数据绑定(data binding)提供了一种简单得多的处理 XML 的方法。
文档模型与数据绑定 本系列中先前的文章所讨论的文档模型(请参阅参考资料)是数据绑定的最接近的替代方案。文档模型和数据绑定都在内存中构建文档表示,同时内部表示和标准的文本 XML 之间可以互相转换。两者之间的不同是文档模型尽可能接近地保存 XML 结构,而数据绑定只关心应用程序使用的文档数据。
为阐明这一点,图 1 显示了一个简单 XML 文档的文档模型视图。文档组件 ? 本例中只有元素和文本节点 ? 按照反映原始 XML 文档的结构链接在一起。将节点的结果树和原始文档关联起来比较容易,但解释树中显示的实际数据就不那么容易。
图 1. 文档的文档模型视图
如果您的应用程序为 XML 使用文档模型方法,您就要处理这种类型的树。在本例中,您将使用节点间的父-子关系纵向浏览该树,并使用公共父节点的多个子节点间的同级关系横向浏览该树。您将能够以非常详细的级别操作树结构,当您把这棵树序列化为文本时,生成的 XML 文档将会反映您所做的更改(比如包含注释)。
现在,把图 1 和图 2 对比一下,会显示同一个文档的数据绑定视图。这里,原始 XML 文档的结构几乎完全被转换隐藏起来了,但通过一对对象,查看和访问实际数据要容易得多。
图 2. 文档的数据绑定视图
使用这个数据结构就是进行正常的 Java 编程 ? 您甚至不必知道关于 XML 的任何知识!(哦,我们在这里不要讨论得太远了 ? 我们的专家顾问还要吃饭呢 ......)参加您这个项目的人至少需要懂得如何建立数据结构和 XML 文档之间的映射,但这仍是简化方向上的一大步。
public class Test { public static void main(String[] argv) {
// build a test bean FlightBean bean = new FlightBean(); bean.setCarrier("AR"); bean.setNumber(426); bean.setDepartureTime("6:23a"); bean.setArrivalTime("8:42a"); try {
// write it out as XML File file = new File("test.xml"); Writer writer = new FileWriter(file); Marshaller.marshal(bean, writer);
// now restore the value and list what we get Reader reader = new FileReader(file); FlightBean read = (FlightBean) Unmarshaller.unmarshal(FlightBean.class, reader); System.out.println("Flight " + read.getCarrier() + read.getNumber() + " departing at " + read.getDepartureTime() + " and arriving at " + read.getArrivalTime());
用 Castor 超越 bean Castor 实际上不仅仅使用本文中所讨论的“类似 JavaBean”类。它还可以用公共成员变量访问简单数据对象类中的信息。例如,对 Test 类稍做更改就可以使用航班数据的下列定义,并仍然以相同的 XML 格式结束:
public class FlightData { public String carrier; public int number; public String departure; public String arrival; } 为使 Castor 正确地使用它,无论如何类必须一直存在。如果类定义了任何 getX 或 setX 方法,Castor 就把它作为 bean 对待并只使用那些方法进行组织和解组。
在这段代码中,您首先构建一个 FlightBean bean 并用一些封装的值对它进行初始化。然后您使用 Castor 的用于 bean 的缺省 XML 映射把 bean 写到输出文件。最后,使用同一个缺省的映射读回生成的 XML 来重新构建 bean,然后从重新构建的 bean 打印出信息。下面是结果:
Flight AR426 departing at 6:23a and arriving at 8:42a
清单 4. 用映射进行组织和解组 ... // write it out as XML (if not already present) Mapping map = new Mapping(); map.loadMapping("mapping.xml"); File file = new File("test.xml"); Writer writer = new FileWriter(file); Marshaller marshaller = new Marshaller(writer); marshaller.setMapping(map); marshaller.marshal(bean);
// now restore the value and list what we get Reader reader = new FileReader(file); Unmarshaller unmarshaller = new Unmarshaller(map); FlightBean read = (FlightBean)unmarshaller.unmarshal(reader); ... } catch (MappingException ex) { ex.printStackTrace(System.err); ...
清单 9. 航班查找程序代码 private static void listFlights(TimeTableBean top, String from, String to, int rating) {
// find the routes for outbound and inbound flights Iterator r_iter = top.getRoutes().iterator(); RouteBean in = null; RouteBean out = null; while (r_iter.hasNext()) { RouteBean route = (RouteBean)r_iter.next(); if (route.getFrom().getIdent().equals(from) && route.getTo().getIdent().equals(to)) { out = route; } else if (route.getFrom().getIdent().equals(to) && route.getTo().getIdent().equals(from)) { in = route; } }
// make sure we found the routes if (in != null && out != null) {
Leave SEA on Arctic Airlines 426 at 6:23a return from LAX on Arctic Airlines 593 at 9:27a Leave SEA on Arctic Airlines 426 at 6:23a return from LAX on Arctic Airlines 102 at 12:30p Leave SEA on Arctic Airlines 433 at 9:00a return from LAX on Arctic Airlines 102 at 12:30p
文档模型比较 在这里我不打算尝试彻底分析使用其中一种 XML 文档模型的等价代码;那样的话太复杂,简直要再单独写一篇文章。解决这个问题的最简单方法可能是首先解析 carrier 元素,然后建立一个把每个标识符代码链接到相应元素的映射。然后,使用与清单 9 中的示例代码相似的逻辑。每一步都比 bean 示例更复杂,因为代码处理的是 XML 组件而不是实际的数据值。性能可能也要差得多 ? 如果您只是对数据执行几个操作就没问题,但如果它位于应用程序的核心处,就是大问题了。
如果在 bean 和 XML 之间的映射中使用更多的数据类型转换,差异(在代码复杂性和性能方面)会更大。例如,如果大量处理航班时间,您可能希望把文本时间转换为更好的内部表示(比如一天内的分钟数,如清单 9 所示)。您可以为文本相对内部格式(把映射设置为只使用文本格式)定义另外的 get 和 set 方法,或者定义一个定制的 org.exolab.castor.mapping.FieldHandler 实现供 Castor 把该实现与这些值一起使用。保持时间值为内部格式允许您在设法匹配清单 9 中的航班时跳过转换,从而使处理速度更快。