級別: 中級
Dennis M. Sosnoski (dms@sosnoski.com), 總裁, Sosnoski Software Solutions, Inc.
2003 年 12 月 01 日
對于主要關心文檔數據內容的應用程序,Java XML 數據綁定是一種代替 XML 文檔模型的強大機制。本文中,企業 Java 專家 Dennis Sosnoski 介紹數據綁定,并討論什么使它如此令人矚目。然后,他向讀者展示了如何利用 Java 數據綁定的開放源代碼 Castor 框架來處理日益復雜的文檔。如果您的應用程序更多的把 XML 作為數據而不是文檔,您就會愿意了解這種處理 XML 和 Java 技術的簡單有效的方法。
應用程序中使用 XML 文檔的多數方法都把重點放在 XML 上:從 XML 的觀點使用文檔,按照 XML 元素、屬性和字符數據內容編程。如果應用程序主要關心文檔的 XML 結構,那么這種方法非常好。對于更關心文檔中所含數據而非文檔本身的許多應用程序而言, 數據綁定提供了一種更簡單的使用 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 類型的證書,因此可在任何類型的應用程序(包括完整版權的項目)中使用。 Castor 實際上僅僅有 XML 數據綁定,它還支持 SQL 和 LDAP 綁定,盡管本文中不討論這些其他的特性。該項目從 2000 年初開始發起,目前處于后 beta 狀態(一般可以使用這個版本,但是如果需要問題修正,您可能需要升級到目前的 CVS 版本)。請參閱 參考資料部分的 Castor 站點鏈接,以了解更多的細節并下載該軟件。
默認綁定
Castor XML 數據綁定很容易上手,甚至不需要定義 XML 文檔格式。只要您的數據用類 JavaBean 的對象表示,Castor 就能自動生成表示這些數據的文檔格式,然后從文檔重構原始數據。
 |
數據綁定詞匯表
下面這個小小的詞匯表列出了本文中要用到的一些術語:
編組是在內存中生成對象的 XML 表示的過程。與 Java 序列化一樣,這種表示需要包括所有依賴的對象:主對象引用的對象、這些對象引用的其他對象、等等。
解組是上述過程的逆過程,在內存中從 XML 表示創建對象(以及依賴的對象)。
映射是用于編組和解組的一些規則。Castor 有一些內建的規則定義了默認映射,本文這一部分將要描述。它也允許您使用單獨的映射文件,參見后述。
|
|
那么“類 JavaBean”是什么意思呢?真正的 JavaBean 是可視化組件,可以在開發環境中配置以用于 GUI 布局。一些源于真正 JavaBean 的慣例已經被 Java 團體普遍接受,特別是對于數據類。如果一個類符合以下慣例,我就稱之為是“類 JavaBean”的:
- 這個類是公共的
- 定義了公共的默認(沒有參數)構造函數
- 定義了公共的
getX 和 setX 方法訪問屬性(數據)值
關于技術定義已經扯得太遠了,當提到這些類 JavaBean 類時,我將不再重復說明,只是稱之為“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,并使用一些固定的數據初始化它。然后用該 bean 默認的 Castor XML 映射將其寫入一個輸出文件。最后又讀回生成的 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 用以下兩種方法中的一個,把該對象的屬性值包括進來:
- 對于具有基本類型值的屬性創建元素的一個屬性(本例中只有
number 屬性通過 getNumber() 方法公開為 int 值)。
- 對于每個具有對象類型值的屬性創建根元素的一個子元素(本例中的所有其他屬性,因為它們是字符串)。
結果就是上面所示的 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 的映射。通過在該元素中加入 auto-complete 屬性并把值設為 true ,您可以告訴 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>
|
一切都和上一個映射(如 清單 3所示)完全相同,只不過用 field 元素定義了一個 RouteBean 的 flights 屬性。這個映射用到了兩個原來不需要的屬性。 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 中的航班時進行轉換,也許還能加快處理速度。
除了本文中所述的之外—— FieldHandler 只是一個例子,Castor 還有許多迷人的特性。但愿這些例子和討論使您能夠初步領略這個框架的強大功能和靈活性。 我相信,您將和我一樣發現 Castor 非常有用也非常有趣。
結束語
對于使用 XML 交換數據的應用程序,數據綁定是文檔模型很好的替代品。它簡化了編程,因為您不必再按照 XML 的方式思考。相反,您可以直接使用代表應用程序所用數據含義的對象。與文檔模型相比,它還潛在地提供了更好的內存和處理器使用效率。
本文中,我使用 Castor 框架展示了一些越來越復雜的數據綁定的例子。所有這些例子都使用所謂的 直接數據綁定:開發人員根據數據定義類,然后把數據映射到 XML 文檔結構。下一篇文章中,我將探討另一種方法: 模式數據綁定,利用模式(如 DTD、XML 模式或者其他的類型)生成和那個模式對應的代碼。
Castor 同時支持模式方法和本文中介紹的直接綁定,您將在以后看到更多的 Castor 應用。我還關注著 JSR-031 Java 數據綁定標準的進展,并對這些方法的性能進行比較。更多了解 Java 中的 XML 數據綁定這個領域,請速來訪問離您最近的 IBM developerWorks。
|