從 XML 到 Java 程式碼的資料繫結(1)(轉)

amyz發表於2007-08-12
從 XML 到 Java 程式碼的資料繫結(1)(轉)[@more@]

  在這個由四部分組成的系列文章的第一部分,我們將弄清什麼是資料繫結,與在 Java 應用程式中處理 XML 資料的其它方法相比它有什麼優勢,以及如何開始使用它。這一部分將考查為什麼使用資料繫結,以及如何為各種約束建立模型,使 XML 文件能轉換成 Java 物件。同時還涵蓋用於生成資料繫結類的輸入和輸出。

  您希望在您的 Java 應用程式中使用 XML 嗎?那麼好,同成千上萬的其他人一起上這條船吧。當您深入瞭解 XML 以後,也許您會發現 DOM 和 SAX API(請參閱 參考資料)不過是唬人的東西。您可能認為 肯定 存在某種簡單方法可以取得 XML 文件,並透過 Java 應用程式訪問它,對嗎? 不必透過回撥或複雜的樹狀結構,而是使用像 setOwner(Stringowner) 和 int getNumOrders() 這樣的方法,對嗎?如果您曾經沿著這一思路考慮問題,那麼資料繫結就是您要尋找的解決方案。

  分析各種選擇

  當今各種 XML 和 XML 主義正氾濫成災(XSL、RDF、名稱空間、RSS、XML Schema、XSLT...),您可能認為現在會有很多方法去訪問 Java 應用程式中的 XML 資料。令人驚訝的是,如果您尋根究底,實際只存在三種訪問 XML 資料的方法。沒錯 -- 只有三種方法,其中的一種還是最近隨一種新的 Java API 才出現的。

  應該這樣來看待這一問題:選擇範圍小使您更易於選出適合於您的方法。

  回撥

  回撥是作為一種事件驅動模型工作的。當分析 XML 文件時,某些事件 -- 如文件的起始和某個元素中的字元資料的起始 -- 將觸發回撥方法。透過使用執行邏輯所需的資料,您可以實現這些事件的 Java 程式碼。要弄清這種方法不能全靠直覺;開發人員通常要花費一段時間來理解和掌握回撥模型的使用。SAX,用於 XML 的一種簡單 API,是這種 XML 使用方法的事實上的標準。

  樹

  更常見、更流行的是這種 API,它們取得一個 XML 文件,然後建立資料的樹狀結構。XML 文件成為樹首,充當一種容器。它有若干子級,如根元素。根元素又有其附加的子級,依此類推,直到(在某種意義上)獲得 XML 資料的一幅圖為止。因為幾乎每個大學生在某個階段肯定都處理過樹狀結構,所以這就可用作表示 XML 資料的一種非常直觀的方法。

  用於 XML 文件樹狀表示的最流行的 API 就是 W3C 的推薦標準,即文件物件模型 (DOM)。一種更新的 API,JDOM (這不是首字母縮寫詞)最近也正一直在推廣並流行開來。 (雖然這個方案是我和 Jason Hunter 建立的,但我還得說實話。)另外,DOM 和 JDOM 都是 Spinnaker 方案設計的基本要求,Spinnaker 是一種新的 XML 分析器,它作為 Apache XML 方案的一部分正在開發之中。

  雖然樹狀 API 看起來比事件驅動的 SAX 更易於使用,但它們並不總是合適的。非常大的文件可能需要大量的記憶體(尤其是使用 DOM 時);當對樹結構執行轉換 (XSLT) 時,系統可能停止運轉甚至徹底崩潰。 雖然更新的 API(如 JDOM)能處理這些問題,但如果您必須處理極大量的資料,它們仍將是一個問題。並且,有時開發人員寧願將 XML 文件中的資料建模為一個簡單的帶有值的讀寫方法的 Java 物件,而不用樹狀模型工作。例如,開發人員會寧願不去訪問名為 skuNumber 的子節點並設定該節點的文字值,而只想呼叫 setSkuNumber("mySKU") 並繼續進行。

  資料繫結

  用 Java 程式碼訪問 XML 資料的最新方法要依賴於一套新的 Java 方法和相關的 API,這些 API 仍在開發之中。資料繫結是由 Sun 構建的一種“Java 規範要求”(JSR-031,見 參考資料),它設計用於使 Java 物件 繫結到 XML 文件更加方便,這樣就使一種格式能夠容易地轉換為另一種格式,反之亦然。繫結引用一個具有讀寫方法的 Java 物件,讀寫方法都會影響到底層的 XML 文件,並且也都直接對映為 XML 文件中的元素及特徵的名稱。當您進入到本系列文章下一部分中的某些細節時,這一說明會更有意義,但在目前,只說一點就夠了:這樣做使 XML 文件特徵 name 能夠透過一個稱為 setName() 的方法,來更改它的值,就像我上面暗示的那樣。

  這種訪問方式正在得到普及,並且當在 XML 文件中儲存配置資訊時特別有用。許多開發人員發現,它非常便於直接訪問所需的引數,而無須使用更復雜的樹狀結構。雖然這種訪問對於文件轉換或訊息傳送沒有什麼用處,但它對於簡單資料處理是極其方便的。它是我們在本文及本系列文章中關注的第三種使用 XML 的方法。

  (當然,任何方法隨後都會引出一系列新的術語,所以請檢視 術語解釋以瞭解這些新的行話。)

  是否任何 XML 文件都可以轉換為 Java 物件?還是僅有某些型別的 XML 文件才可以? 問得好!您很可能只希望將滿足一組約束條件的文件轉換為 Java 物件。這與定義 Java 介面的方法類似:您確保只例項化和使用適應該介面的物件,允許就如何操作該物件作出假定。同樣,您只允許將滿足一組約束條件的 XML 物件轉換成 Java 物件;這使您能夠按希望的方式來使用所建立的物件。

  附:

  (術語解釋

  資料繫結。從 Java 應用程式內部訪問 XML 資料的一種新方法,使用仍在開發中的一種 API,JSR-031。

  JSR-031。Sun 仍在開發中的一種新的 Java 規範請求,設計用於將 XML 文件編譯成一個或多個 Java 類,而在 Java 應用程式中可以方便地使用這些 Java 類。

  打包 。 將 Java 物件轉換為 XML 表示,擁有當前值。

  解包 。 根據 XML 物件建立 Java 物件,通常是根據打包生成一個 Java 物件。)

  約束資料

  在研究程式碼之前,您需要回答幾個有關如何表示 XML 資料的問題。這是資料繫結的最具挑戰性的方面之一。是為每個文件建立一個新類,還是建立某個現有類的一個例項?您要使用哪個現有類?並且最重要的是,您正在處理的文件是否適宜轉換為 Java 物件?

  那是一大堆問題,但您將在這裡找到全部答案。將這些問題看作一系列決策點,一系列選擇。首先,您必須確定您能否從該 XML 文件建立 Java 物件(如前面所討論的那樣)。如果能,您就要決定轉換應該以新 Java 類的形式出現,還是僅以現有類的一個例項的形式出現。最後,如果選擇了現有類,那麼使用哪個類呢?結果就是各種各樣的決策樹。

  如果我們考察清單 1 中所示的一個示例 XML 文件,然後再來處理這些問題,則決策樹的意義就更加清楚了。此示例文件表示 Enhydra Application Server 中某個服務(具體說就是一個 web 容器)的配置。

  清單 1. 一個用於配置 Enhydra 中的 web 容器的 XML 文件

  

  此配置文件包含有關服務本身的版本和名稱的資訊,以及幾個巢狀的專案,每個專案都表示有關該 web 容器服務的一些附加資訊。它給出了有關埠的詳細資訊(包括埠號、協議和安全性),也給出了文件服務資訊(包括文件根、用於索引頁的預設副檔名以及錯誤頁)。所有這些合在一起,就是配置一個新的 web 容器服務所需的全部資訊。

  記住這個示例,您就可以開始回答資料表示的各個問題了。

  是否適合轉換?

  絕對適合!只要看一看 清單 1中的 XML 文件就會發現,它表示一個物件(總體配置物件),具有若干特徵或變數。其中某些變數又是另外的物件(埠和文件),這些物件又具有它們自己的特徵。實際上,這是適合轉換為 Java 物件的 XML 文件的一個極好例子。為了進一步保證此物件是可用的,稍後我將向您展示一種方法來約束文件中的資料。但是,還是先讓我們繼續沿著決策樹往下走。

  轉換成類還是例項?

  解決適宜性問題以後,現在就可以作出決定,是將每個 XML 配置文件都變為一個全新的 Java 類呢,還是簡單地將其變為某個現有類的一個新例項。換句話說,就是到底應該生成新程式碼,還是利用現有的程式碼。照這樣來看,這就變成了一個簡單的可重用性問題。更容易且更明智的做法是,為每個 XML 文件生成現有類的新例項。如果您 一定要嘗試一下從每個文件建立一個新的 Java 類,則得到的各個類之間可能沒有相容性 -- 即兩個完全相同的文件可能導致兩個不同的 Java 類!

  不用這個可能引起混亂的方法,您可以採用一組 XML 約束條件(由一個 DTD 或 XML 方案表示,將在下面講述),並根據這些約束條件來生成一個 Java 類(或多個類,根據需要)。這個生成的類將表示符合這些約束條件的任何 XML 文件;這些 XML 文件中的每一個都將被解包到生成的類的一個例項中。在這種情況下,就可以為表示 web 服務配置的文件定義約束條件。 這些約束條件將被對映為一個 Java 類,我們將稱之為 WebServiceConfiguration 。 然後您就可以獲得任何一種表示特定 web 服務配置的 XML 文件,並假定此文件符合我們的約束條件,由它而建立出前面生成的類的一個例項。這將允許應用程式將不同的 XML 文件用作相同型別的物件,只要這些文件中的資料對於該物件設計時要達到目的來說是有效的即可。

  新類還是現有的類?

  現在您也已經有條件回答下一個問題了:您希望建立一個現有類即 WebServiceConfiguration 類的一個例項。剩下需要弄清的全部事情是,這個類是如何預先生成的。 所以,現在請將您的注意力集中在這樣一個問題上:如何獲得一組約束條件,用 XML 實現它們,並保證文件符合這些約束?再一個問題就是,您如何再從這些約束條件生成一個可重用的 Java 類?

  利用文件約束條件

  既然您知道此文件將轉換成一個 Java 例項,這就產生了另一個問題:要考慮到必須以某種方式保證可將此文件正確地解包到一個選定的 Java 類中。缺少變數或資料型別不正確都可能導致在解包過程中出錯 -- 或者甚至在客戶機訪問配置錯誤的容器時出現執行時異常。

  最好的情況是,在實際的解包過程開始之前,文件的作者能夠保證,配置文件對於他們選擇用來表示資料的類是“合法的”。閱讀到這一方案的 XML 人士說不定就會轉動他們的眼睛並嘀咕說,“好吧,當然您將使用 XML 文件約束條件。”確認資料對選定類的合法性可以透過引用 DTD (文件型別定義)或 XML 方案來完成。

  透過使用一組用外部 DTD 或方案檔案表示的約束條件,文件作者就可以在這些資料的“介面”上測試配置資料。換句話說,您可以這樣來建立應用程式,使之能夠對照所需的資料來檢查包含在 XML 例項文件中的資料,而所需資料則是在文件約束條件的外部檔案中指定的。 這樣,您就可以為資料建立一個介面。

  在使用 DTD 方案還是使用 XML 方案之間作出決策是一種相當簡單的選擇:因為 Java 語言是高度型別化的,所以我們希望能在 XML 文件中支援型別化。例如,資料介面應該能夠驗證,為 web 容器的埠號提供的是整數,而不是字串,後者在服務啟動時將引起錯誤。DTD 不支援型別檢查,所以我們無疑應該選擇 XML 方案。雖然 XML 方案在規範的領域在有一點不確定性,但它在很大程度上已趨於穩定,並可望在年內定型。我們可以在一個 XML 方案中編寫資料約束條件,然後用這個方案驗證可能的例項文件,以確保解包能在這些例項文件上進行。下面的 XML 方案表示我們的 web 容器服務的資料介面。

  清單 2. 表示一個 web 容器配置文件的資料介面的 XML 方案

  

  清單 2 中的 XML 方案定義了幾個不同的資料物件,它們合起來表示一個 web 服務的配置物件。首先,定義了一個核心服務配置( serviceConfiguration ),它包含版本和名稱。這可用作所有服務(如負載均衡服務、EJB 容器,當然還有我們的 web 服務)的基本物件。然後,作為此基本服務的擴充套件,又定義了 web 服務配置( webServiceConfiguration )。請注意,Java 成型之後,方案就已經為資料介面建立了模型。我們將附加的 web 服務屬性 port 和 document 新增到 version 和 name 基本屬性中。這些屬性本身都是物件,具有它們自己的屬性( protocol 、 root 、 error 等)。

  在此方案的定義方式中,特徵代表簡單的 Java 型別,通常是原始 (primitive) 型別。這樣, name 和 version 就分別成為型別 String 和 float 的 Java 原始型別。 port 和 document 這樣的元素成為 Java 物件,它們可以有自己的屬性,還是用特徵來表示。 這樣就出現了遞迴現象:元素變成新物件,並對它的每個屬性進行檢查。如果屬性是一個特徵,就為此物件建立一個簡單的 Java 原始成員變數;如果屬性是元素,則建立一個新的物件,並作為一個成員變數將其新增,然後在這個新物件上又開始同樣的過程,直到全部類都已建立為止。

  從蘿蔔 ... 嗯 ... XML 獲得 Java

  一旦建立了 XML 方案,您就需要從這個方案中提取出必需的資訊,來確定應該建立哪些 Java 類。這個過程的第一步是檢視 XML 方案,並嚴格確定輸出應該是什麼。對於簡單的 serviceConfiguration 物件,定義了兩個 Java 原始屬性: name 和 version 。 對於這樣一個簡單的物件,確定所需的介面並不難。 只需將被定義型別的名稱的首字母大寫,並將這些 Java 屬性新增到介面中即可,如清單 3 所示。

  清單 3. 為 ServiceConfiguration 介面而從 XML 方案生成的 Java 程式碼

  public interface ServiceConfiguration {public void setVersion(float version);public float getVersion();public void setName(String name);public String getName();}

  這是相當明白易懂的; 清單 3中的介面為 XML 方案中定義的屬性提供讀方法和寫方法。另外,您將需要生成一個實現類來定義此介面的各個成員變數,並實現此介面中的每個方法。這種使介面從實現中分離出來的方法使我們能夠為特定的需要提中供多種實現。 例如,某個特定的服務可能需要執行計算,而不只是接受從寫方法中收到的值。 現在考慮那種更復雜的情況還有點為時尚早,但我將在後續文章中重新提到它。然而,一般說來,您仍可以確定實現類應該像什麼樣子,如清單 4 所示。

  清單 4. 為 ServiceConfiguration 實現而從 XML 方案生成的 Java 程式碼

  public class ServiceConfigurationImpl implements ServiceConfiguration {private String name;private float version;public void setVersion(float version) {this.version = version;}public float getVersion() {return version;}public void setName(String name) {this.name = name;}public String getName() {return name;}}

  相同的原則也適用於 XML 方案中定義的其它物件。您可以在下面檢視到其它 Java 類(因為它們都是應該生成的):

  • PortType.java (文章開始的原始碼包中附原始碼)
  • PortTypeImpl.java (文章開始的原始碼包中附原始碼)
  • DocumentType.java (文章開始的原始碼包中附原始碼)
  • DocumentTypeImpl.java (文章開始的原始碼包中附原始碼)
  • WebServiceConfiguration.java (文章開始的原始碼包中附原始碼)
  • WebServiceConfigurationImpl.java (文章開始的原始碼包中附原始碼)

  總結

  到目前為止,您應該對資料繫結的各個方面都比較熟悉了。 我已初步介紹了您應該使用資料繫結的原因,尤其是在配置資訊的範圍內,並概述了為實現此方法您所需要了解的一些基本概念。

  此係列文章的下一篇將繼續考察資料繫結的過程。您將有機會去檢查 org.enhydra.xml.binding.SchemaMapper 類,它將接受這第一部分中建立的 XML 方案作為資料介面,並從它建立出一個 Java 介面和實現類。本系列文章的第二部分將詳細說明這一過程的每個步驟,並說明如何確保方案被準確表示,以便 XML 文件能接著被轉換為生成的類的例項。

  梳理一下您學到的 XML 方案和 JDOM (我將在示例程式碼中使用它們),下個月再見!


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752019/viewspace-950140/,如需轉載,請註明出處,否則將追究法律責任。

相關文章