对于主要关心文档的数据内容的应用程序来说,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 文档的结构几乎完全被转换隐藏起来了,但通过一对对象,查看和访问实际数据要容易得多。
使用这个数据结构就是进行正常的 Java 编程 ? 您甚至不必知道关于 XML 的任何知识!(哦,我们在这里不要讨论得太远了 ? 我们的专家顾问还要吃饭呢 ......)参加您这个项目的人至少需要懂得如何建立数据结构和 XML 文档之间的映射,但这仍是简化方向上的一大步。
数据绑定还可以提供其它的好处,并不是只简化编程。由于它把许多文档细节抽象出来,所以数据绑定所需的内存通常少于文档模型方法所需的内存。例如,请考虑一下前面图中显示的两个数据结构:文档模型方法使用 10 个单独的对象,而数据绑定使用两个。由于要构建的东西少得多,所以为文档构建数据绑定表示还可能更快。最后,使用数据绑定方法访问程序内的数据要比使用文档模型快得多,因为您可以控制如何表示和存储数据。稍后我再回到这些问题。
如果数据绑定这么好,那么您何时想使用文档模型代替它呢?在两种情况下需要使用文档模型:
您的应用程序真正关心文档结构的细节。例如,如果您正在编写一个 XML 文档编辑器,您会希望一直使用文档模型而不用数据绑定。
您正在处理的文档不遵守固定的结构。例如,数据绑定不会是实现一般 XML 文档数据库的好方法。
许多应用程序使用 XML 进行数据传送,但在其它方面它们就不关心文档表示的细节问题了。这些应用程序是数据绑定的理想候选者。如果您的应用程序适合这种模式,请继续阅读。
Castor 框架
目前,有许多不同的框架支持 Java 的 XML 数据绑定,但却没有一个标准接口。这一点最终会改变:Java Community Process(JCP)中的 JSR-031 正致力于定义一个标准(请参阅参考资料)。暂时我们先选择一个框架学习使用它的接口。
在本文中,我选择使用 Castor 数据绑定框架。Castor 项目使用一个 BSD 类型的许可证,使得它可用于所有类型的应用程序(包括完全归私人所有的应用程序)。通过支持 SQL 和 LDAP 绑定,Castor 在 XML 数据绑定之外实际上也运行得很好,尽管在本文中我将忽略这些其它的功能。从 2000 上半年就已经开始对 Castor 进行开发了,目前正处于高级 beta 测试版状态(一般情况下可用,但如果需要错误修订的话,您可能需要更新到当前的 CVS 版本)。请参阅参考资料部分以获得到 Castor 站点的链接,了解更多的详细信息或者下载该软件。
缺省绑定
开始使用 Castor 的 XML 数据绑定非常简单。您甚至不需要定义一个 XML 文档格式。只要您的数据出现在“类似 JavaBean”(JavaBean-like)对象中,Castor 就会生成一种文档格式来自动表示数据并且稍后从那个文档重新构建原始数据。
数据绑定字典
下面是我在本文中使用的一些术语的一个微型字典:
组织(Marshalling)是为内存中的对象生成 XML 表示的过程。与 Java 序列化一样,这种表示需要包含所有依赖的对象:我们的主对象引用的对象、那些对象引用的对象,等等。
解组(Unmarshalling)是与组织相反的过程,在内存中根据 XML 表示构建一个对象(和依赖的对象)。
映射(Mapping)是用于组织和解组的一套规则。 Castor 有内置的规则定义本文的这一部分所描述的缺省映射。它还允许您使用单独的映射文件,在下面的部分您将看到这一点。
那么“类似 JavaBean”是什么意思呢?真正的 JavaBeans 是一些可视组件,可以在开发环境中配置它们以便在 GUI 布局中使用。一些用真正的 JavaBeans 开始的惯例在 Java 社区中已经变得非常普及,尤其是对于数据类。如果一个类遵守下列惯例,我就称其为“类似 JavaBean”:
该类是公共的
它定义了一个公共的缺省(无参数)构造函数
它定义了公共的 getX 和 setX 方法用来访问属性(数据)值
既然技术上的定义不成问题,在谈及这些“类似 JavaBean”类的其中一个时,我将跳过所有这些,并只称呼它为“bean”类。
我将使用航线航班时间表为例来讲解贯穿本文的代码,并从一个表示特定航班的简单 bean 类开始来阐明它的工作机制。这个 bean 包含四个信息项:
运输商(航空公司)标识符
航班号
起飞时间
到达时间
下面的清单 1 显示了用来处理航班信息的代码。
清单 1. 航班信息 bean
public class FlightBean
{
private String m_carrier;
private int m_number;
private String m_departure;
private String m_arrival;
public FlightBean() {}
public void setCarrier(String carrier) {
m_carrier = carrier;
}
public String getCarrier() {
return m_carrier;
}
public void setNumber(int number) {
m_number = number;
}
public int getNumber() {
return m_number;
}
public void setDepartureTime(String time) {
m_departure = time;
}
public String getDepartureTime() {
return m_departure;
}
public void setArrivalTime(String time) {
m_arrival = time;
}
public String getArrivalTime() {
return m_arrival;
}
}
您可以看到,这个 bean 自身非常枯燥乏味,所以我想添加一个类并在缺省的 XML 绑定中使用这个类,如清单 2 所示。
清单 2. 缺省的数据绑定测试
import java.io.*;
import org.exolab.castor.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());
} catch (IOException ex) {
ex.printStackTrace(System.err);
} catch (MarshalException ex) {
ex.printStackTrace(System.err);
} catch (ValidationException ex) {
ex.printStackTrace(System.err);
}
}
}
用 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
这个输出说明您已经成功地对航班信息进行了一次读、写来回(对于只用了两次方法调用来说已经不错了)。现在,我将再深入一些,而不是只进行控制台输出。
幕后情况
要更多地了解这个示例中发生了什么事,请看一下 Marshaller.marshal() 调用生成的 XML。下面是这个文档:
<?xml version="1.0"?>
<flight-bean number="426">
<arrival-time>8:42a</arrival-time>
<departure-time>6:23a</departure-time>
<carrier>AR</carrier>
</flight-bean>
Castor 使用 Java 内省检查您传入 Marshaller.marshal() 调用的对象。在这个例子中,它找到了您定义的四个属性值。Castor 在输出 XML 中创建一个元素(文档的根元素)代表对象整体。在本例中,该元素的名称源自对象类名,flight-bean。然后 Castor 用两种方法的其中之一包含进这个对象的属性值。它创建:
每个基本数据类型值属性作为元素的一个属性(在这个例子中,只有 getNumber() 方法公开的 int 值 number 属性)
每个对象值属性作为根元素的子元素(这里是所有的其它子元素,因为它们是字符串)。
结果是上面立即显示的 XML 文档。
更改 XML 格式
如果您不喜欢 Castor 的缺省映射格式,您可以很容易地更改映射。例如,在我们的航班信息示例中,假设我们需要数据的更紧凑的表示。使用属性代替子元素会帮助实现这一点,我们甚至可能希望使用比缺省值更短的名称。与下面所示的文档相似的文档将非常适合我们的需要:
<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
定义映射
要让 Castor 使用这种格式而不用缺省的格式,首先需要定义一个描述这种格式的映射。映射描述本身就是(非常惊讶吧)一个 XML 文档。清单 3 显示了把 bean 组织为以前显示的格式的映射。
清单 3. 用来压缩格式的映射
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Basic mapping example</description>
<class name="FlightBean" auto-complete="true">
<map-to xml="flight"/>
<field name="carrier">
<bind-xml name="carrier" node="attribute"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping>
class 元素为已命名的类(在本例中是 FlightBean)定义映射。您可以通过在这个元素中包含值为 true 的可选 auto-complete 属性告诉 Castor 为元素主体内没有明确列出的类的任何属性都使用缺省的映射。这一点在这里很方便,因为早已经按您希望的方式处理过 number 属性了。
子 map-to 元素告诉 Castor 把 FlightBean 类的实例映射为 XML 文档中的 flight 元素。如果您继续使用在幕后情况中的缺省映射输出示例中看到的 flight-bean 的缺省元素名,那么这个元素就是可选的。
最后,您可以为您想用不同于缺省属性的处理方法处理的每个属性包含一个子 field 元素。所有这些都遵守同一个模式:name 属性给出属性名,子 bind-xml 元素告诉 Castor 如何映射该属性。在本例中,您告诉它把每个属性都映射为带有给定名称的属性。
使用映射
既然已经有了定义好的映射,那么您就需要告诉 Castor 框架在组织和解组数据时使用该映射。清单 4 显示为实现这一点需要对先前的代码做的更改。
清单 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);
...
这段代码比先前清单 2 中所示的使用缺省映射的代码复杂了一点。在进行任何其它操作之前,请先创建一个 Mapping 对象并加载您的映射定义。实际的组织和解组也是不一样的。要使用映射,需要创建 Marshaller 和 Unmarshaller 对象,用映射配置它们,并调用这些对象的方法而不是调用第一个示例中的静态方法。最后,必须处理映射错误生成的另一种异常类型。
完成这些更改后,您可以再次尝试运行这个测试程序。控制台输出与第一个示例一样(如清单 2 所示),但现在 XML 文档看起来正如您希望的那样:
<?xml version="1.0"?>
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
处理集合
既然单个的航班数据是用的您喜欢的格式,那么您可以定义更高级的结构:路线数据。这个结构将包含往返机场的标识符以及那条路线上的一个航班集合。清单 5 显示了拥有这些信息的 bean 类的一个示例。
清单 5. 路线信息 bean
import java.util.ArrayList;
public class RouteBean
{
private String m_from;
private String m_to;
private ArrayList m_flights;
public RouteBean() {
m_flights = new ArrayList();
}
public void setFrom(String from) {
m_from = from;
}
public String getFrom() {
return m_from;
}
public void setTo(String to) {
m_to = to;
}
public String getTo() {
return m_to;
}
public ArrayList getFlights() {
return m_flights;
}
public void addFlight(FlightBean flight) {
m_flights.add(flight);
}
}
在这段代码中,我已经定义了一个 addFlight() 方法用来在路线上一次一个设置航班。这是在测试程序中构建数据结构的一种很方便的方法,但与您预料的相反,Castor 在解组时并不使用这种方法向路线添加航班。相反,它使用 getFlights() 方法访问航班集合,然后直接向该集合添加航班。
在映射中处理航班集合只需使用上个示例(如清单 3 所示)中使用的 field 元素的变体。清单 6 显示了修改过的映射文件。
清单 6. 用于带航班集合的路线的映射
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Collection mapping example</description>
<class name="RouteBean">
<map-to xml="route"/>
<field name="from">
<bind-xml name="from" node="attribute"/>
</field>
<field name="to">
<bind-xml name="to" node="attribute"/>
</field>
<field name="flights" collection="collection" type="FlightBean">
<bind-xml name="flight"/>
</field>
</class>
<class name="FlightBean" auto-complete="true">
<field name="carrier">
<bind-xml name="carrier" node="attribute"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping>
除定义 RouteBean 的 flights 属性的 field 元素之外,一切都与上一个映射(如清单 3 所示)完全相同。这个映射使用前面不需要的一对属性。带有值 collection 的 collection 属性将这个属性定义为一个 java.util.Collection(其它值定义数组、java.util.Vectors 等)。type 属性定义包含在集合中的对象的类型,用全限定类名作为值。这里的值就是 FlightBean,因为我没有使用类包。
其它的不同是我不再需要使用 FlightBean 类元素内的 map-to 子元素为绑定定义元素名。定义 RouteBean 的 flights 属性的 field 元素通过它的子 bind-xml 元素做这项工作。既然组织和解组 FlightBean 对象的唯一方法是通过这个属性,它们将一直使用这个 bind-xml 元素设置的名称。
我不想找麻烦去显示这个示例的测试程序,因为它的数据绑定部分与上个示例的相同。下面是为一些样本数据生成的 XML 文档的样子:
<?xml version="1.0"?>
<route from="SEA" to="LAX">
<flight carrier="AR" depart="6:23a" arrive="8:42a"
number="426"/>
<flight carrier="CA" depart="8:10a" arrive="10:52a"
number="833"/>
<flight carrier="AR" depart="9:00a" arrive="11:36a"
number="433"/>
</route>
对象引用
现在,您终于准备好要处理整个航班时间表。为此,您将向集合中再添加三个 bean:
AirportBean 用来提供机场信息
CarrierBean 用来提供航线信息
TimeTableBean 用来满足一切要求
为使它比较有趣,除上个示例(在处理集合中显示)中使用的 RouteBean 和 FlightBean 之间的所有权关系之外,您还要添加一些 bean 间的链接。
链接 bean
对于第一个添加的关系,将 FlightBean 更改为直接引用运输商信息而不是只使用代码标识运输商。下面是 FlightBean 的变化:
public class FlightBean
{
private CarrierBean m_carrier;
...
public void setCarrier(CarrierBean carrier) {
m_carrier = carrier;
}
public CarrierBean getCarrier() {
return m_carrier;
}
...
}
现在,为 RouteBean 做同样的工作使其引用机场信息:
public class RouteBean
{
private AirportBean m_from;
private AirportBean m_to;
...
public void setFrom(AirportBean from) {
m_from = from;
}
public AirportBean getFrom() {
return m_from;
}
public void setTo(AirportBean to) {
m_to = to;
}
public AirportBean getTo() {
return m_to;
}
...
}
我不会引入被添加的 bean 本身的代码,因为它们显示的内容前面都已经做过了。您可以下载 code.jar 下载文件中所有示例的完整的代码(请参阅参考资料)。
映射引用
您将需要使用映射文档的其它一些功能来支持您正在组织和解组的对象间的引用。清单 7 显示了完整的映射:
清单 7. 整个时间表的映射
<!DOCTYPE databases PUBLIC
"-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
"http://castor.exolab.org/mapping.dtd">
<mapping>
<description>Reference mapping example</description>
<class name="TimeTableBean">
<map-to xml="timetable"/>
<field name="carriers" type="CarrierBean" collection="collection">
<bind-xml name="carrier"/>
</field>
<field name="airports" type="AirportBean" collection="collection">
<bind-xml name="airport"/>
</field>
<field name="routes" type="RouteBean" collection="collection">
<bind-xml name="route"/>
</field>
</class>
<class name="CarrierBean" identity="ident" auto-complete="true">
<field name="ident">
<bind-xml name="ident" node="attribute"/>
</field>
</class>
<class name="AirportBean" identity="ident" auto-complete="true">
<field name="ident">
<bind-xml name="ident" node="attribute"/>
</field>
</class>
<class name="RouteBean">
<field name="from" type="AirportBean">
<bind-xml name="from" node="attribute" reference="true"/>
</field>
<field name="to" type="AirportBean">
<bind-xml name="to" node="attribute" reference="true"/>
</field>
<field name="flights" type="FlightBean" collection="collection">
<bind-xml name="flight"/>
</field>
</class>
<class name="FlightBean" auto-complete="true">
<field name="carrier">
<bind-xml name="carrier" node="attribute" reference="true"/>
</field>
<field name="departureTime">
<bind-xml name="depart" node="attribute"/>
</field>
<field name="arrivalTime">
<bind-xml name="arrive" node="attribute"/>
</field>
</class>
</mapping>
除被添加的 bean 之外,这里重要的改变是添加了 identity 和 reference 属性。class 元素的 identity 属性告诉 Castor 已命名的属性是该类的一个实例的唯一标识符。这里,我让 CarrierBean 和 AirportBean 都把它们的 ident 属性定义为标识符。
bind-xml 元素的 reference 属性提供 Castor 进行映射所需的链接信息的另一部分。reference 设置为 true 的映射告诉 Castor 为引用的对象组织标识符,而不要对象自身的副本。我已经为路线两端从 RouteBean 到链接的 AirportBean 的引用,以及从 FlightBean 到链接的 CarrierBean 的引用使用了这种技术。
当 Castor 使用这种类型的映射解组数据时,它自动把对象标识符转换为对实际对象的引用。您需要确保标识符的值是真正唯一的,即便是不同类型对象的标识符也不能重复。对于这个示例中的数据,这不是问题:运输商标识符是两个字符,机场标识符是三个字符,所以它们永远也不会相同。当确实有可能冲突的情况发生时,您可以轻易地避开这个问题,只要为每个标识符添加前缀,这个前缀是代表标识符所表示的对象类型的唯一代码。
已组织的时间表
除设置了更多样本数据外,这个示例在测试代码中并没有包含什么新内容。清单 8 显示了组织操作生成的 XML 文档:
清单 8. 已组织的时间表
<?xml version="1.0"?>
<timetable>
<carrier ident="AR" rating="9">
<URL>;http://www.arcticairlines.com<;/URL>
<name>Arctic Airlines</name>
</carrier>
<carrier ident="CA" rating="7">
<URL>;http://www.combinedlines.com<;/URL>
<name>Combined Airlines</name>
</carrier>
<airport ident="SEA">
<location>Seattle, WA</location>
<name>Seattle-Tacoma International Airport</name>
</airport>
<airport ident="LAX">
<location>Los Angeles, CA</location>
<name>Los Angeles International Airport</name>
</airport>
<route from="SEA" to="LAX">
<flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>
<flight carrier="CA" depart="8:10a" arrive="10:52a" number="833"/>
<flight carrier="AR" depart="9:00a" arrive="11:36a" number="433"/>
</route>
<route from="LAX" to="SEA">
<flight carrier="CA" depart="7:45a" arrive="10:20a" number="311"/>
<flight carrier="AR" depart="9:27a" arrive="12:04p" number="593"/>
<flight carrier="AR" depart="12:30p" arrive="3:07p" number="102"/>
</route>
</timetable>
处理数据
既然已经最终设置了时间表的所有数据,我们来快速看一下如何在程序中操作这些数据。使用数据绑定,您已经为时间表构建了一个数据结构,该时间表由几种类型的 bean 组成。处理这些数据的应用程序代码可以直接使用这些 bean。
例如,假设您想查找西雅图和洛杉矶之间的往返旅程选择,但只限于被指定为最低等级的运输商。清单 9 显示了使用数据绑定 bean 结构获取这些信息的基本代码(关于完整的详细信息,请参阅参考资料中的源代码下载)。
清单 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) {
// find outbound flights meeting carrier rating requirement
Iterator o_iter = out.getFlights().iterator();
while (o_iter.hasNext()) {
FlightBean o_flight = (FlightBean)o_iter.next();
if (o_flight.getCarrier().getRating() >= rating) {
// find inbound flights meeting carrier rating
// requirement, and leaving after outbound arrives
int time = timeToMinute(o_flight.getArrivalTime());
Iterator i_iter = in.getFlights().iterator();
while (i_iter.hasNext()) {
FlightBean i_flight = (FlightBean)i_iter.next();
if (i_flight.getCarrier().getRating() >= rating
&&
timeToMinute(i_flight.getDepartureTime())
> time) {
// list the flight combination
printFlights(o_flight, i_flight, from, to);
}
}
}
}
}
}
您可以使用清单 8 中早就显示的样本数据进行试验。如果您查询从西雅图(SEA)到洛杉矶(LAX),运输商级别等于或高于 8 的航班,结果如下:
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 中的航班时跳过转换,从而使处理速度更快。
Castor 提供了许多用于定制的 hook,但在本文的讨论范围之外:特殊的 FieldHandler 只是一个示例。理想情况下,样本代码和讨论应该已经使您感觉到该框架的功能和灵活性。我鼓励您自己进一步试验 Castor。我想您会像我一样发现 Castor 那样有用(那样有趣)。
结束语
数据绑定是使用 XML 进行数据交换的应用程序中的文档模型的一个非常不错的替代方案。它简化了您的编程,因为您不需要再按照 XML 去考虑问题。相反,您可以直接处理表示应用程序使用的数据的意义的对象。它还提供使内存和处理器性能比使用文档模型时更好的潜力。
在本文中,我已经讨论了一系列越来越复杂的、使用 Castor 框架的数据绑定示例。所有这些示例都使用我所谓的直接(direct)数据绑定:开发者根据数据定义类,然后将数据映射为 XML 文档结构。在下一篇文章中,我将探讨另一种方法:模式(schema)数据绑定,它使用文档模式(比如 DTD、XML Schema 或另一种形式)并生成与该模式相应的代码。
Castor 同时支持模式方法和您在本文中看到的直接绑定,所以在后面的文章中您会看到 Castor 的更多信息。我还将看一下致力于 Java 数据绑定(Java Data Binding)标准的 JSR-031 的进展并比较这两种方法的性能。请留心这个领域了解更多关于 Java 中 XML 数据绑定的信息,不久它就会出现在您身边的 IBM developerWorks 上