对于主要关心文档的数据内容的应用程序来说, 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
文档之间的映射,但这仍是简化方向上的一大步。
数据绑定还可以提供其它的好处,并不是只简化编程。由于它把许多文档细节抽象出来,所以数据绑定所需的内存通常少于文档模型方法所需的内存。例如,请考虑一下前面图中显示的两个数据结构:文档模型方法使用
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
上