文檔選項(xiàng)
將此頁(yè)作為電子郵件發(fā)送

將此頁(yè)作為電子郵件發(fā)送


拓展 Tomcat 應(yīng)用

下載 IBM 開源 J2EE 應(yīng)用服務(wù)器 WAS CE 新版本 V1.1


級(jí)別: 中級(jí)

Dennis M. Sosnoski (dms@sosnoski.com), 總裁, Sosnoski Software Solutions, Inc.

2003 年 12 月 01 日

對(duì)于主要關(guān)心文檔數(shù)據(jù)內(nèi)容的應(yīng)用程序,Java XML 數(shù)據(jù)綁定是一種代替 XML 文檔模型的強(qiáng)大機(jī)制。本文中,企業(yè) Java 專家 Dennis Sosnoski 介紹數(shù)據(jù)綁定,并討論什么使它如此令人矚目。然后,他向讀者展示了如何利用 Java 數(shù)據(jù)綁定的開放源代碼 Castor 框架來處理日益復(fù)雜的文檔。如果您的應(yīng)用程序更多的把 XML 作為數(shù)據(jù)而不是文檔,您就會(huì)愿意了解這種處理 XML 和 Java 技術(shù)的簡(jiǎn)單有效的方法。

應(yīng)用程序中使用 XML 文檔的多數(shù)方法都把重點(diǎn)放在 XML 上:從 XML 的觀點(diǎn)使用文檔,按照 XML 元素、屬性和字符數(shù)據(jù)內(nèi)容編程。如果應(yīng)用程序主要關(guān)心文檔的 XML 結(jié)構(gòu),那么這種方法非常好。對(duì)于更關(guān)心文檔中所含數(shù)據(jù)而非文檔本身的許多應(yīng)用程序而言, 數(shù)據(jù)綁定提供了一種更簡(jiǎn)單的使用 XML 的方法。

文檔模型與數(shù)據(jù)綁定

本系列文章的上一篇(請(qǐng)參閱 參考資料)所討論的文檔模型,是與數(shù)據(jù)綁定最接近的替代方案。文檔模型和數(shù)據(jù)綁定都在內(nèi)存中建立文檔的表示,都需要在內(nèi)部表示和標(biāo)準(zhǔn)文本 XML 之間雙向轉(zhuǎn)換。兩者的區(qū)別在于文檔模型盡可能保持 XML 結(jié)構(gòu),而數(shù)據(jù)綁定只關(guān)心應(yīng)用程序所使用的文檔數(shù)據(jù)。

為了說明這一點(diǎn),圖 1 給出了一個(gè)簡(jiǎn)單 XML 文檔的數(shù)據(jù)模型視圖。文檔成分——在這個(gè)例子中只有元素和文本節(jié)點(diǎn)——通過反映原始 XML 文檔的結(jié)構(gòu)連接在一起。形成的節(jié)點(diǎn)樹很容易和原始文檔聯(lián)系,但要解釋樹中表示的實(shí)際數(shù)據(jù)就不那么容易了。



圖 1. 文檔的文檔模型視圖
文檔的文檔模型視圖

如果應(yīng)用程序使用 XML 文檔模型方法,您就需要處理這種類型的樹。這種情況下,您將使用節(jié)點(diǎn)之間的父子關(guān)系在樹的上下層之間導(dǎo)航,使用屬于同一父節(jié)點(diǎn)的子女之間的兄弟關(guān)系在樹的同一層中導(dǎo)航。您可以非常詳盡地處理樹結(jié)構(gòu),當(dāng)把樹序列化為文本時(shí),生成的 XML 文檔將反映您所做的修改(比如插入的注釋)。

現(xiàn)在來看看與圖 1 截然不同的圖 2,它表示同一文檔的數(shù)據(jù)綁定視圖。在這里,轉(zhuǎn)換過程幾乎隱藏了原始 XML 文檔的所有結(jié)構(gòu),但是因?yàn)橹挥型ㄟ^兩個(gè)對(duì)象,更容易看清楚真正的數(shù)據(jù),也更很容易訪問這些數(shù)據(jù)。



圖 2. 文檔的數(shù)據(jù)綁定視圖
文檔的數(shù)據(jù)綁定視圖

使用這種數(shù)據(jù)結(jié)構(gòu)就像是一般的 Java 編程——甚至根本不需要知道 XML!(哦,還是不要走得 遠(yuǎn)了——我們這些專家顧問還得活……)您的項(xiàng)目中至少要有人明白,這種數(shù)據(jù)結(jié)構(gòu)和 XML 文檔之間的映射是如何建立的,但這仍然是向簡(jiǎn)化邁出的一大步。

僅僅是編程的簡(jiǎn)化,數(shù)據(jù)綁定還帶來其他的好處。與文檔模型方法相比,因?yàn)槌榈袅嗽S多文檔細(xì)節(jié),數(shù)據(jù)綁定通常需要的內(nèi)存更少。比如前面兩個(gè)圖中所示的數(shù)據(jù)結(jié)構(gòu):文檔模型方法使用了 10 個(gè)單獨(dú)的對(duì)象,與此相比數(shù)據(jù)綁定只使用了兩個(gè)。要?jiǎng)?chuàng)建的東西少,構(gòu)造文檔的數(shù)據(jù)綁定表示可能就更快一些。最后,數(shù)據(jù)綁定與文檔模型相比,應(yīng)用程序可以更快地訪問數(shù)據(jù),因?yàn)槟梢钥刂迫绾伪硎竞痛鎯?chǔ)數(shù)據(jù)。我后面還要講到這一點(diǎn)。

既然數(shù)據(jù)綁定那么好,為何還要使用文檔模型呢?以下兩種情況需要使用文檔模型:

  • 應(yīng)用程序真正關(guān)注文檔結(jié)構(gòu)的細(xì)節(jié)。比方說,如果您在編寫一個(gè) XML 文檔編輯器,您就會(huì)堅(jiān)持使用文檔模型而非數(shù)據(jù)綁定。
  • 您處理的文檔沒有固定的結(jié)構(gòu)。比如實(shí)現(xiàn)一種通用的 XML 文檔數(shù)據(jù)庫(kù),數(shù)據(jù)綁定就不是一種好辦法。

許多應(yīng)用程序使用 XML 傳輸數(shù)據(jù),但并不關(guān)心文檔表示的細(xì)節(jié)。這類應(yīng)用程序非常適合使用數(shù)據(jù)綁定。如果您的應(yīng)用程序符合這種模式,請(qǐng)繼續(xù)讀下去。





回頁(yè)首


Castor 框架

目前有幾種不同的框架支持 Java XML 數(shù)據(jù)綁定,但還沒有標(biāo)準(zhǔn)的接口。這種情況最終會(huì)得到改變:Java Community Process (JCP) 的 JSR-031 正在努力定義這方面的標(biāo)準(zhǔn)(請(qǐng)參閱 參考資料)。現(xiàn)在讓我們選擇一個(gè)框架并學(xué)習(xí)使用它的接口。

本文選擇了 Castor 數(shù)據(jù)綁定框架。Castor 項(xiàng)目采用 BSD 類型的證書,因此可在任何類型的應(yīng)用程序(包括完整版權(quán)的項(xiàng)目)中使用。 Castor 實(shí)際上僅僅有 XML 數(shù)據(jù)綁定,它還支持 SQL 和 LDAP 綁定,盡管本文中不討論這些其他的特性。該項(xiàng)目從 2000 年初開始發(fā)起,目前處于后 beta 狀態(tài)(一般可以使用這個(gè)版本,但是如果需要問題修正,您可能需要升級(jí)到目前的 CVS 版本)。請(qǐng)參閱 參考資料部分的 Castor 站點(diǎn)鏈接,以了解更多的細(xì)節(jié)并下載該軟件。





回頁(yè)首


默認(rèn)綁定

Castor XML 數(shù)據(jù)綁定很容易上手,甚至不需要定義 XML 文檔格式。只要您的數(shù)據(jù)用類 JavaBean 的對(duì)象表示,Castor 就能自動(dòng)生成表示這些數(shù)據(jù)的文檔格式,然后從文檔重構(gòu)原始數(shù)據(jù)。

數(shù)據(jù)綁定詞匯表

下面這個(gè)小小的詞匯表列出了本文中要用到的一些術(shù)語(yǔ):

編組是在內(nèi)存中生成對(duì)象的 XML 表示的過程。與 Java 序列化一樣,這種表示需要包括所有依賴的對(duì)象:主對(duì)象引用的對(duì)象、這些對(duì)象引用的其他對(duì)象、等等。

解組是上述過程的逆過程,在內(nèi)存中從 XML 表示創(chuàng)建對(duì)象(以及依賴的對(duì)象)。

映射是用于編組和解組的一些規(guī)則。Castor 有一些內(nèi)建的規(guī)則定義了默認(rèn)映射,本文這一部分將要描述。它也允許您使用單獨(dú)的映射文件,參見后述。

那么“類 JavaBean”是什么意思呢?真正的 JavaBean 是可視化組件,可以在開發(fā)環(huán)境中配置以用于 GUI 布局。一些源于真正 JavaBean 的慣例已經(jīng)被 Java 團(tuán)體普遍接受,特別是對(duì)于數(shù)據(jù)類。如果一個(gè)類符合以下慣例,我就稱之為是“類 JavaBean”的:

  • 這個(gè)類是公共的
  • 定義了公共的默認(rèn)(沒有參數(shù))構(gòu)造函數(shù)
  • 定義了公共的 getXsetX 方法訪問屬性(數(shù)據(jù))值

關(guān)于技術(shù)定義已經(jīng)扯得太遠(yuǎn)了,當(dāng)提到這些類 JavaBean 類時(shí),我將不再重復(fù)說明,只是稱之為“bean”類。

在整篇文章中,我將使用航線班機(jī)時(shí)刻表作為示例代碼。我們從一個(gè)簡(jiǎn)單的 bean 類開始說明它的工作原理,這個(gè)類表示一個(gè)特定的航班,包括四個(gè)信息項(xiàng):

  • 飛機(jī)編號(hào)(航空公司)
  • 航班編號(hào)
  • 起飛時(shí)間
  • 抵達(dá)時(shí)間

下面的清單 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;
                        }
                        }

您可以看到,這個(gè) bean 本身沒有什么意思,因此我想增加一個(gè)類并在默認(rèn)的 XML 綁定中使用它,如清單 2 所示。



清單 2. 測(cè)試默認(rèn)的數(shù)據(jù)綁定
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

實(shí)際上,Castor 不僅僅能用于本文所述的類 JavaBean 類。它也可以訪問帶有公共成員變量的簡(jiǎn)單數(shù)據(jù)對(duì)象類的數(shù)據(jù)。比如,稍微改動(dòng)前述的 Test 類,您就可以對(duì)航班數(shù)據(jù)使用如下的定義,并最終得到同樣的 XML 格式:

public class FlightData
                                                {
                                                public String carrier;
                                                public int number;
                                                public String departure;
                                                public String arrival;
                                                }

為了使 Castor 正常工作,一個(gè)類必須全部采用這種方式或那種方式。如果類定義了 任何 getXsetX 方法,Castor 就將其視作 bean,并在編組和解組時(shí)只使用這些方法。

這段代碼首先構(gòu)造了一個(gè) FlightBean bean,并使用一些固定的數(shù)據(jù)初始化它。然后用該 bean 默認(rèn)的 Castor XML 映射將其寫入一個(gè)輸出文件。最后又讀回生成的 XML, 同樣使用默認(rèn)映射重構(gòu) bean,然后打印重構(gòu)的 bean 中的信息。結(jié)果如下:

Flight AR426 departing at 6:23a and arriving at 8:42a

這個(gè)輸出結(jié)果表明您已經(jīng)成功地來回轉(zhuǎn)換了航班信息(不算太糟,只有兩次方法調(diào)用)。現(xiàn)在我還不滿足于簡(jiǎn)單控制臺(tái)輸出,準(zhǔn)備再往深處挖一挖。

幕后

為了更清楚地了解這個(gè)例子中發(fā)生了什么,看一看 Marshaller.marshal() 調(diào)用生成的 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 內(nèi)部檢查機(jī)制檢查 Marshaller.marshal() 調(diào)用傳遞的對(duì)象。在本例中,它發(fā)現(xiàn)了定義的四個(gè)屬性值。Castor 在輸出的 XML 中創(chuàng)建一個(gè)元素(文檔的根元素)表示整個(gè)對(duì)象。元素名從對(duì)象的類名中衍生出來,在這里是 flight-bean 。然后Castor 用以下兩種方法中的一個(gè),把該對(duì)象的屬性值包括進(jìn)來:

  • 對(duì)于具有基本類型值的屬性創(chuàng)建元素的一個(gè)屬性(本例中只有 number 屬性通過 getNumber() 方法公開為 int 值)。
  • 對(duì)于每個(gè)具有對(duì)象類型值的屬性創(chuàng)建根元素的一個(gè)子元素(本例中的所有其他屬性,因?yàn)樗鼈兪亲址?

 

結(jié)果就是上面所示的 XML 文檔。





回頁(yè)首


改變 XML 格式

如果不喜歡 Castor 的默認(rèn)映射格式,您可以方便地改變映射。在我們的航班信息例子中,比方說,假定我們需要更緊湊的數(shù)據(jù)表示。使用屬性代替子元素有助于實(shí)現(xiàn)這個(gè)目標(biāo),我們也許還希望使用比默認(rèn)的名字更短一些的名字。如下所示的文檔就可以很好地滿足我們的需要:

<?xml version="1.0"?>
                        <flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>

定義映射

為了讓 Castor 使用這種格式而非默認(rèn)的格式,首先需要定義描述這種格式的映射。映射描述本身(非常意外的)是一個(gè) 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 元素定義了一個(gè)命名類 FlightBean 的映射。通過在該元素中加入 auto-complete 屬性并把值設(shè)為 true ,您可以告訴 Castor 對(duì)于該類的任何屬性,只要沒有在這個(gè)元素中專門列出,就使用默認(rèn)映射。這樣非常簡(jiǎn)便,因?yàn)?number 屬性已經(jīng)按照希望的方式處理了。

子元素 map-to 告訴 Castor,要把 FlightBean 類的實(shí)例映射為 XML 文檔中的 flight 元素。如果您繼續(xù)使用默認(rèn)的元素名 flight-bean (參閱 幕后小節(jié)中默認(rèn)映射輸出的例子),可以不使用該元素。

最后,對(duì)于每個(gè)希望以非默認(rèn)方式處理的屬性,可以引入一個(gè) field 子元素。這些子元素都按照相同的模式: name 屬性給出映射的屬性名, bind-xml 子元素告訴 Castor 如何映射那個(gè)屬性。這里要求把每個(gè)屬性映射成給定名稱的屬性。

使用映射

現(xiàn)在已經(jīng)定義了一個(gè)映射,您需要告訴 Castor 框架在編組和解組數(shù)據(jù)時(shí)使用那個(gè)映射。清單 4 說明了要實(shí)現(xiàn)這一點(diǎn),需要對(duì)前面的代碼做哪些修改。



清單 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默認(rèn)映射所用的代碼相比,這段代碼稍微復(fù)雜一點(diǎn)。在執(zhí)行任何其他操作之前,首先要?jiǎng)?chuàng)建一個(gè) Mapping 對(duì)象載入您的映射定義。真正的編組和解組也有區(qū)別。為了使用這個(gè)映射,您需要?jiǎng)?chuàng)建 MarshallerUnmarshaller 對(duì)象,用定義的映射配置它們,調(diào)用這些對(duì)象的方法,而不是像第一個(gè)例子那樣使用靜態(tài)方法。最后,您必須提供對(duì)映射錯(cuò)誤產(chǎn)生的另一個(gè)異常類型的處理。

完成這些修改后,您可以嘗試再次運(yùn)行程序。控制臺(tái)輸出與第一個(gè)例子相同(如 清單 2所示),但是現(xiàn)在的 XML 文檔看起來符合我們的需要:

<?xml version="1.0"?>
                        <flight carrier="AR" depart="6:23a" arrive="8:42a" number="426"/>





回頁(yè)首


處理集合

現(xiàn)在單個(gè)航班數(shù)據(jù)已經(jīng)有了我們喜歡的形式,您可以定義一個(gè)更高級(jí)的結(jié)構(gòu):航線數(shù)據(jù)。這個(gè)結(jié)構(gòu)包括起降機(jī)場(chǎng)的標(biāo)識(shí)符以及在該航線上飛行的一組航班。清單 5 給出了一個(gè)包含這些信息的 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);
                        }
                        }

在這段代碼中,我定義了一個(gè) addFlight() 方法,用于每次增加一個(gè)屬于這條航線的航班。這是在測(cè)試程序中建立這種數(shù)據(jù)結(jié)構(gòu)非常簡(jiǎn)便的辦法,但是可能和您預(yù)料的相反, Castor 在解組時(shí)并不使用種方法向航線中增加航班。相反,它使用 getFlights() 方法訪問一組航班,然后直接添加到集合中。

在映射中處理航班集合只需要稍微改變上一個(gè)例子(如 清單 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>

一切都和上一個(gè)映射(如 清單 3所示)完全相同,只不過用 field 元素定義了一個(gè) RouteBeanflights 屬性。這個(gè)映射用到了兩個(gè)原來不需要的屬性。 collection 屬性的值 collection 把該屬性定義成一個(gè) java.util.Collection (其他值分別定義數(shù)組,java.util.Vectors 等等)。 type 屬性定義包含在集合中的對(duì)象類型,值是完整的限定類名。這里的值是 FlightBean ,因?yàn)閷?duì)這些類我沒有使用包。

另一個(gè)區(qū)別在 FlightBean 類元素中,不再需要使用 map-to 子元素定義綁定的元素名。定義 RouteBeanflights 屬性的 field 元素,通過它的 bind-xml 子元素定義了這一點(diǎn)。因?yàn)榫幗M或解組 FlightBean 對(duì)象只能通過該屬性,它們將永遠(yuǎn)使用這個(gè) bind-xml 元素設(shè)定的名稱。

我不再詳細(xì)列出這個(gè)例子的測(cè)試程序,因?yàn)閿?shù)據(jù)綁定部分和上一個(gè)例子相同。以下是用一些示例數(shù)據(jù)生成的 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>





回頁(yè)首


對(duì)象引用

現(xiàn)在可以為處理完整的航班時(shí)刻表做最后的準(zhǔn)備了。您還需要增加三個(gè) bean:

  • AirportBean 用于用于機(jī)場(chǎng)信息
  • CarrierBean 用于航線信息
  • TimeTableBean 把一切組合起來

為了保持趣味性,除了上一個(gè)例子(參閱 處理集合)中用到的 RouteBeanFlightBean 之間的從屬關(guān)系,您還要在 bean 之間增加一些聯(lián)系。

連接 bean

要增加的第一個(gè)聯(lián)系是修改 FlightBean ,讓它直接引用班機(jī)信息,而不再僅僅用代碼標(biāo)識(shí)班機(jī)。以下是對(duì) FlightBean 的修改:

public class FlightBean
                        {
                        private CarrierBean m_carrier;
                        ...
                        public void setCarrier(CarrierBean carrier) {
                        m_carrier = carrier;
                        }
                        public CarrierBean getCarrier() {
                        return m_carrier;
                        }
                        ...
                        }

然后對(duì) RouteBean 做同樣的修改,讓它引用機(jī)場(chǎng)信息:

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 自身的代碼,因?yàn)楹颓懊娴拇a相比沒有什么新鮮的東西。您可以從下載文件 code.jar 中找到完整的示例代碼(請(qǐng)參閱 參考資料)。

映射引用

您可能需要映射文檔的其他一些特性,以支持編組和解組的對(duì)象之間的引用。清單 7 給出了一個(gè)完整的映射:



清單 7. 完整的時(shí)刻表映射
<!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 之外,這里有一個(gè)重要的變化,就是增加了 identityreference 屬性。 class 元素的 identity 屬性,通知 Castor 這個(gè)命名屬性是該類實(shí)例的唯一標(biāo)識(shí)符。在這里,我把 CarrierBeanAirportBeanident 屬性定義成它們的標(biāo)識(shí)符。

bind-xml 元素的 reference 屬性,提供了對(duì)于該映射 Castor 所需要的另一部分鏈接信息。 reference 設(shè)為 true 的映射告訴 Castor 編組和解組引用對(duì)象的標(biāo)識(shí)符,而不是對(duì)象本身的副本。從 RouteBean 鏈接 AirportBean (表示航線的起止點(diǎn))的引用,從 FlightBean 鏈接 CarrierBean 的引用,都使用了這種方法。

當(dāng) Castor 使用這種類型的映射解組數(shù)據(jù)時(shí),它自動(dòng)把對(duì)象標(biāo)識(shí)符轉(zhuǎn)化為對(duì)實(shí)際對(duì)象的引用。您需要保證標(biāo)識(shí)符的值確實(shí)是唯一的,甚至不同類型的對(duì)象之間也要保證這種唯一性。對(duì)于本例中的數(shù)據(jù),這一點(diǎn)不成問題:飛機(jī)的標(biāo)識(shí)符是兩個(gè)字符,而機(jī)場(chǎng)的標(biāo)識(shí)符是三個(gè)字符,永遠(yuǎn)不會(huì)沖突。如果 確實(shí)有潛在沖突的可能性,只要在所代表的對(duì)象類型的每個(gè)標(biāo)識(shí)符加上唯一的前綴,就可以很容易地避免這種問題。

編組后的時(shí)刻表

這個(gè)例子的測(cè)試代碼沒有新東西,只是增加了一些示例數(shù)據(jù)。清單 8 給出了編組形成的 XML 文檔:



清單 8. 編組的時(shí)刻表
<?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>





回頁(yè)首


使用數(shù)據(jù)

現(xiàn)在,時(shí)刻表中的所有數(shù)據(jù)都最終完成了,簡(jiǎn)單地看一看如何在程序中處理它們。使用數(shù)據(jù)綁定,您已經(jīng)建立了時(shí)刻表的數(shù)據(jù)結(jié)構(gòu),它由幾種類型的 bean 組成。處理數(shù)據(jù)的應(yīng)用程序代碼可以直接使用這些 bean。

比方說,假設(shè)您要查看在西雅圖和洛杉磯之間有哪些航班可供選擇,并且要求班機(jī)至少具備指定的最低品質(zhì)評(píng)價(jià)級(jí)別。清單 9 給出了使用數(shù)據(jù)綁定 bean 結(jié)構(gòu)獲取這些信息的基本代碼(完整的細(xì)節(jié)請(qǐng)參閱從 參考資料下載的源文件)。



清單 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中的數(shù)據(jù)。如果您詢問從西雅圖(SEA)到洛杉磯(LAX)、級(jí)別大于或等于 8 的班機(jī),就會(huì)得到如下的結(jié)果:

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

與文檔模型的比較

這里我不準(zhǔn)備全面討論使用 XML 文檔模型的等價(jià)代碼,那太復(fù)雜了,足以單獨(dú)成章。解決這個(gè)問題最簡(jiǎn)單的方式,可能是首先解析 carrier 元素,創(chuàng)建每個(gè)標(biāo)識(shí)符代碼到相應(yīng)對(duì)象之間的映射鏈接。然后使用和 清單 9中示例代碼類似的邏輯。和使用 bean 的例子相比,每一步都更加復(fù)雜,因?yàn)榇a使用的是 XML 成分而不是真正的數(shù)據(jù)值。性能可能更糟——只對(duì)數(shù)據(jù)進(jìn)行少量的操作還不算是問題,但是如果數(shù)據(jù)處理是應(yīng)用程序的核心,這就會(huì)成為一個(gè)主要的焦點(diǎn)。

如果在 bean 和 XML 的映射中使用更多的數(shù)據(jù)類型轉(zhuǎn)換,差別會(huì)更大(無(wú)論從代碼的復(fù)雜性還是從性能的角度看)。比方說,假設(shè)您使用很多的航班時(shí)間,可能希望把文本時(shí)間轉(zhuǎn)化成一種更好的國(guó)際化表示(如一天內(nèi)的分鐘數(shù),參見 清單 9)。您可以選擇為文本和國(guó)際化格式定義可以替換的 getset 方法(讓映射僅僅使用文本形式),也可以定義一個(gè)定制的 org.exolab.castor.mapping.FieldHandler 實(shí)現(xiàn)讓 Castor 使用這些值。保留時(shí)間值的內(nèi)部形式,可以避免匹配清單 9 中的航班時(shí)進(jìn)行轉(zhuǎn)換,也許還能加快處理速度。

除了本文中所述的之外—— FieldHandler 只是一個(gè)例子,Castor 還有許多迷人的特性。但愿這些例子和討論使您能夠初步領(lǐng)略這個(gè)框架的強(qiáng)大功能和靈活性。 我相信,您將和我一樣發(fā)現(xiàn) Castor 非常有用也非常有趣。





回頁(yè)首


結(jié)束語(yǔ)

對(duì)于使用 XML 交換數(shù)據(jù)的應(yīng)用程序,數(shù)據(jù)綁定是文檔模型很好的替代品。它簡(jiǎn)化了編程,因?yàn)槟槐卦侔凑?XML 的方式思考。相反,您可以直接使用代表應(yīng)用程序所用數(shù)據(jù)含義的對(duì)象。與文檔模型相比,它還潛在地提供了更好的內(nèi)存和處理器使用效率。

本文中,我使用 Castor 框架展示了一些越來越復(fù)雜的數(shù)據(jù)綁定的例子。所有這些例子都使用所謂的 直接數(shù)據(jù)綁定:開發(fā)人員根據(jù)數(shù)據(jù)定義類,然后把數(shù)據(jù)映射到 XML 文檔結(jié)構(gòu)。下一篇文章中,我將探討另一種方法: 模式數(shù)據(jù)綁定,利用模式(如 DTD、XML 模式或者其他的類型)生成和那個(gè)模式對(duì)應(yīng)的代碼。

Castor 同時(shí)支持模式方法和本文中介紹的直接綁定,您將在以后看到更多的 Castor 應(yīng)用。我還關(guān)注著 JSR-031 Java 數(shù)據(jù)綁定標(biāo)準(zhǔn)的進(jìn)展,并對(duì)這些方法的性能進(jìn)行比較。更多了解 Java 中的 XML 數(shù)據(jù)綁定這個(gè)領(lǐng)域,請(qǐng)速來訪問離您最近的 IBM developerWorks