数据绑定在今天的 java 技术和 xml 编程世界中虽然是老生常谈,但仍然有很大的误解。这个专栏抛开所有空洞的理论,把重点放在应用数据绑定需要了解的概念上。您将了解一般数据绑定和 XML 世界中的数据绑定之间的区别,以及往返、语义等价和对数据绑定软件包的要求。 既然阅读本专栏,就说明您至少对 XML 数据绑定有一定的爱好。若是在一年前,我就不得不定义数据绑定是什么,深入到复杂的概念,这是一项令人厌倦的工作,通常要花费几页的篇幅。不过现在是 2004 年,而不是一年以前了,基本上所有目前从事 XML 和 Java 开发的人员的思想中,数据绑定似乎已经扎下了根。由于某些原因,坦白地讲,这令我感到困惑,甚至连 SAX 和 DOM 代表什么也不知道的 XML 新手都在天天使用数据绑定。因此对于您(单击这篇文章标题的人)的背景知识,我并不像在过去所能做到的那样肯定。
一般的数据绑定 首先,必须暂时放一放 XML 数据绑定(得承认,您满脑子想的都是这个),来看看更一般意义上的数据绑定。虽然多数程序员都认为数据绑定就是 XML 文档和某种 Java 编程结构之间的转换过程,但这仅仅是数据绑定技术的一种具体应用。在展开这个复杂的问题之前,最好先看一看更广阔的背景。
传统的数据绑定 简单地讲,数据绑定是指取出一些数据(比如从 XML 文档、文本文件或者数据库中)并通过程序表示这些数据的过程??把数据绑定 到虚拟机(VM)能够理解并且可以操作的某种内存中结构。相应地,数据绑定软件包应该能够用 VM 上修改的数据更新底层存储媒介(XML 文档、文件或数据库分区)中的数据。假如连这些最少 的功能都不能实现,就称不上数据绑定软件包??至少从这个词的传统意义上讲不是的。
// Get an instance of a data binding factory Factory factory = DBFactory.newInstance(); factory.connect(); List employees = factory.unmarshal(DatabaseConstants.EMPLOYEE_TABLE);
// Manipulate data in employee objects for (Iterator i = employees.iterator; i.hasNext(); ) { Employee employee = (Employee)i.next(); System.out.println("First name: " + employee.getFirstName()); System.out.println("Last name: " + employee.getLastName()); // etc... }
比数据库更常见的是将此类 API 用于 XML,从 XML 文档中提取数据。这就是问题的由来:程序员如此关注 XML 上的数据绑定而非一般意义上的数据绑定,以至于经常把通用 API 看作是针对特定格式的。比如,清单 2 中的代码和 清单 1 是等价的,最近我发现很多咨询建议中使用这类代码。
清单 2. 使用数据绑定 API 访问 XML 文档
// Get an instance of a data binding factory Factory factory = XMLFactory.newInstance(); factory.connect(); List employees = factory.unmarshal(XMLConstants.EMPLOYEE_DOCUMENT);
// Manipulate data in employee objects for (Iterator i = employees.iterator; i.hasNext(); ) { Employee employee = (Employee)i.next(); System.out.println("First name: " + employee.getFirstName()); System.out.println("Last name: " + employee.getLastName());
非凡要注重粗体显示的两行??这里把 XML 语义引入到了实际的数据绑定应用中。这种做法非常糟糕,会令您急得手忙脚乱。在使用(和选择)数据绑定 API 时,应该寻找通用的 API,而不是专用的 API。这就是为何我要花费您这么多宝贵的时间,介绍各种数据绑定 API 的互换性,来说明所谓的具体细节实际上是实现的细节,而不是 API 的细节 。假如在代码中暴露 XML 语义和数据库语义,就可能得到编写很差、实现很差或者两者都很差的数据绑定 API。从一种存储媒介改为另一种存储媒介应该很简单,对代码的影响很小。不要再做 Java 和 XML 数据绑定程序员了,开始转变为数据绑定程序员吧。您会发现您的代码和所实现的功能比过去更加健壮,更加灵活。
虽然 API 是专用的,但仍然要使用 一些人可能在这里已经看到了一线曙光,但是仍然要使用特定的数据绑定 API 或实现。您可能使用类似清单 2 的代码,或者其他类似的东西,在不需要的的地方公开数据格式结构。那么好吧??实际上可以通过少量的工作来获得前述的效果。这时应考虑编写一个包装程序 API。包装程序 API 位于开发人员和数据绑定 API(它大概不能很好地完成自己的工作)之间。您可以包装有问题的功能,比如 getAttribute() 或 getChild() 调用(这是 XML 专用的),编写新的类调用这些方法,并使用更加有意义而且存储中立的方法名。还应该考虑预备一个工厂类,以便很轻易从 XML 媒介转到数据库媒介(或者有一天可能会用到的其他媒介)。这不是一项轻松的任务,但有一定经验的开发人员仍然可以完成。我将在这里说明如何创建包装程序 API,但是我计划在本专栏以后的文章中还会讨论这个话题。
重要的定义 理解了通用数据绑定 API 的用处以后,您可能已经预备学习如何正确地选择和使用数据绑定 API。在这篇入门介绍的最后,我们将学习一些您可能还不熟悉的定义。这些概念对于保证 API 的正确运行(尤其是在处理 XML 时)至关重要;了解这些定义还可以避免造成 API 输出的 XML 和输入不相符。
编组(Marshalling)是把内存中的数据转化到存储媒介上的过程。因此在 Java 和 XML 环境中,编组就是把一些 Java 对象转化成一个(或多个) XML 文档。在数据库环境中,则是把 Java 表示的数据存入数据库。显然,编组的秘密在于把 Java 实例中的面向对象结构转化成适用于 XML 的扁平结构,或者 RDBMS 中的关系结构(使用 Java 技术转换到 OODBMS 实际上很简单)。
解组(Unmarshalling)是把数据从存储媒介转换到内存中的过程??正好与编组相反。因此需要把 XML 文档解组到 Java VM 中。这里的复杂性不是在扁平数据中,因为这不是必需的,而在于从正确的数据到正确的 Java 代码变量的映射。假如映射是错误的,就不可能正确地访问数据。当然,假如再尝试重新编组还会造成更大的问题,并且问题传播得很快。
往返 往返(Round-tripping)可能是最重要也最轻易误解的数据绑定术语。往返用于描述从存储媒介到内存然后回到存储媒介的完整循环。在 XML 和 Java 技术环境中,这就意味着从 XML 文档到 Java 实例变量,然后再回到 XML 文档。正确的往返要求,假如中间没有修改数据,XML 输入和 XML 输出应该是等同的。换句话说,清单 3 中的文档 input.xml 和 output.xml 应该基本上相同。
清单 3. 往返
// Get an instance of a data binding factory Factory factory = XMLFactory.newInstance(); factory.connect(); List employees = factory.unmarshal("input.xml");
employees.marshal("output.xml");
假如输入和输出不能正确地匹配,就说明往返中存在问题,实际应用这种 API 很快就会陷入困境。假如把数据传入 VM 内存时不能依靠 API 正确地保持数据,您基本上就陷入了绝境。下一篇文章中将分析 Sun JAXB API 的往返能力,看看是否能实现期望的功能。
语义等价 因为 XML 是数据的文本格式,它有一些有趣的怪癖。比方说,XML 处理(和忽略)空白的方式不是由自身决定的。是否存在 DTD 或者模式影响到空白的处理;CDATA 元素的使用影响实体处理;一个编辑器中的缩进编排格式到另一个编辑器中可能会成为一团乱麻。
这些还不够,XML 对数据及其顺序有非常非凡的规则。在 XML 文档中,元素属性的顺序不重要。因为这种无序性和空白处理的不同,两个看起来有天壤之别的文档可能实际上包含完全相同的数据。在这种情况下,说两个文档“相同”或者“相等”实际上是不严格的。这就引入了另一个术语:语义等价。就是说,从文档中所含数据和涉及的结构的角度来看,两个文档是相同。尽管文档看起来可能彼此不同,但从数据的角度看是等价的。比如,看看清单 4 中的代码段。
您可能已经看到,这两个清单包含着同样的数据,事实上它们在语义上是等价的。因为这个例子很简单,所以等价性不难确定。但是,假如 XML 文档有成百上千条记录,每个元素都有多个属性(都能以任意的顺序出现),就很难判定语义上的等价性。这也是数据绑定框架中最困难的部分??想想上一节所述的往返。正确的往返不要求输入和输出文档看起来一致,只要求它们在语义上是等价的。因此现在要指出的是,假如说 API 能正确地往返,这就要求您了解 XML 的基础,并且保证您的 API 完全遵循这些规则。
虽然一些 API 提供了各种各样的机制定制输出,但为了保证语义等价,不一定要使用这种选项。和往返一样,我也预备在下一篇文章中分析 JAXB 的语义等价处理,看看输入和输出文档在结构上的区别。我还将考察文档在解组和编组过程中发生的任何变化。
结束语 可能读完了本文后,有些读者感到它没有讲如何做什么事情。但是,我认为假如花点时间把握了数据绑定的基础,非凡是往返和语义等价,您会发现在使用数据绑定 API 时,您可以编写出更好的、最终也更快的程序。这也是深入学习下一篇文章??JAXB API 的基础。同时,您也可以使用喜欢的 API 作一些简单的测试,看看它是否(理想地)保持了语义等价性。好了,下一篇文章再见。