實現 Castor 數據綁定--轉

第 1 部分: 安裝和設置 Castor

數據綁定風靡一時

在 XML 新聞組、郵件列表和網站的討論論壇中(在 參考資料 中能夠找到這些內容的連接),最多見的一個主題就是數據綁定。Java 和 XML 開發人員正在尋求一種在 Java 對象與 XML 文檔之間來回轉換的簡單方法。html

Sun 藉助其 JAXB,即 Java Architecture for XML Binding(若是您在其餘地方看到縮寫詞 JAXB,那也是正常的;Sun 彷佛每一年都會更改 JAXB 所表明的含義), 在數據綁定領域佔據了主導地位。然而,JAXB API(若是您喜歡,也能夠稱爲架構)存在着一些不足,而且更新速度較慢。它也不能處理到關係數據庫的映射,而這種映射是一種很常見的請求。java

Castor 的誕生

正是在這種情形下,Castor 出現了。Castor 是一種開源框架,它可用於沒法使用 JAXB 的領域。Castor 一直在發展之中,而且早於 JAXB 代碼庫和 SUN 數據綁定規範。實際上,Castor 已經實現了更新,可結合 JAXB 方法實現數據綁定,所以使用 JAXB 的編程人員能夠很容易地移動代碼。node

Castor 的優點

在討論安裝和使用 Castor 的細節以前,有必要指出嘗試 Castor 以及從 JAXB 轉變到 Castor 的理由。程序員

  • 首先,Castor 幾乎是 JAXB 的替代品。換句話說,能夠輕易地將全部 JAXB 代碼轉變爲 Castor(並非徹底取代,可是足以使剛剛接觸 Castor 的程序員輕鬆完成任務)。
  • 其次,Castor 在數據綁定領域提供了許多的功能,無需使用模式即可在 Java 和 XML 之間進行轉換,提供一種比 JAXB 更易於使用的綁定模式,以及可以對關係數據庫和 XML 文檔進行編組(marshal)和解組(unmarshal)。
  • Castor 還提供了 JDO 功能。JDO 也就是 Java Data Objects,是驅動 Java-to-RDBMS 編組和解組的底層技術。儘管再也不像前幾年那麼流行,JDO 仍然是一個不錯的功能。此外,因爲 JDO 也是一種 Sun 規範,所以不用編寫模糊的 API。
 

下載 Castor

Castor 的安裝過程很簡單。首先,訪問 Castor Web 站點(參見 參考資料 中的連接)並在左側菜單中單擊 Download。選擇 latest milestone release,而後向下滾動到 Download sets。您能夠下載 Castor JAR、DTD、doc、dependency 等全部內容,預打包的下載套件很是容易使用(參見圖 1)。apache

圖 1. Castor Web 站點的下載套件

Castor 的下載套件將二進制文件和 doc 文件打包成易於使用的單獨歸檔文件

在本文中,咱們將使用版本 1.1.2.1。我選擇 ZIP 格式的 The Castor JARs, docs, DTDs, command line tools, and examples 下載套件。您將得到一個能夠展開的歸檔文件,其中包含許多 JAR 文件、文檔和示例程序(參見圖 2)。編程

圖 2. 展開的 Castor 歸檔文件

Castor 包含許多 JAR 文件、文檔以及示例程序

 

正確放置全部文件

接下來,須要將 Castor 的全部文件安放在系統中的正確位置,使您的 Java 環境可以訪問它們。api

將 Java 庫放在同一個位置

我強烈建議將全部第三方 Java 庫放在一個常見位置。您能夠將它們隨意散放在系統中,可是這樣作會帶來嚴重後果,由於以下緣由:數組

  1. 在大多數狀況下很難找到須要的東西。
  2. 您將會花大量時間來肯定使用的庫版本,由於您會常常將多個版本放在系統的不一樣位置。
  3. 類路徑將會變得很長並且難於理解。

我將個人全部庫放在 /usr/local/java/ 中,每一個庫放在本身的子目錄中(各個目錄一般帶有一個版本號)。所以將 Castor 歸檔文件 — 通過擴展 — 移動到您經常使用的庫位置。在本例中,Castor 的完整路徑爲:/usr/local/java/castor-1.1.2.1

爲 Castor JavaDoc 添加書籤

在系統中設置 Java 庫的另外一個步驟是定位和連接到文檔。您會常常這樣作,並且大多數 Java 庫都提供文檔的本地副本(包括 JavaDoc),使用 HTML 格式。在 Castor 中,這個路徑是 castor-1.1.2.1/doc/。所以在個人系統中,我爲 /usr/local/java/castor-1.1.2.1/doc/index.html 添加了一個書籤。圖 3 顯示了本地載入的 Castor 文檔外觀,版本爲 1.1.2.1。

圖 3. 本地載入的 Castor 文檔

文檔中的大多數連接也引用本地頁面

在本文中以及在您平常編程中都須要執行這些操做,緣由有二:

  1. 文檔是本地的。 在飛機上編寫過程序嗎?是否是沒有網絡連接?不能登陸到 Starbucks WiFi 吧?本地文檔除了可以更快速地訪問以外,在這些情形中也發揮着重要做用。
  2. 本地文檔老是適合您本身的須要。隨着 Castor 的不斷髮展,您也許不會常常下載最新的發行版。使用在線文檔就意味着使用最新版本的文檔,這可能與您系統中的版本不匹配。當使用本地文檔時,使用的文檔老是和當前使用的庫版本對應。所以,不會因爲使用不恰當或者庫版本中根本不存在的特性而引發混亂和挫折。
 

下載 Castor 依賴項

Castor 有許多 依賴項:

  • Apache Ant
  • Jakarta Commons
  • JDBC 2.0 Standard Extensions
  • Log4J 登陸實用程序
  • Apache Xerces XML 解析程序

這其實是適用於大多數 Castor 操做的一個精簡的集合。若是想從頭構建 Castor、運行這些示例、使用這些測試,或者更深刻地研究 Castor,還須要更多的依賴項。必須將這些依賴項放到類路徑中,才能使用 Castor。

複雜的方法

要運行 Castor,比較麻煩的方法就是,首先訪問 Castor 的下載頁面並記下所需的每一個依賴項的版本。而後跳轉到每一個依賴項的 Web 站點,找到該版本,下載並將其添加到類路徑中。這種方法會花費較長的 時間,可是更加易於控制。此外,若是您已經設置好並能正常運行大多數庫,這仍然是一個可行的方法。

簡單的方法

幸運的是,還有一種更好的方法。回到 Castor 的下載頁面,找到穩定版本,並定位到另外一個下載套件,這個下載套件叫作 Full SVN snapshot: All sources, docs, and 3rd party libraries (big)。儘管標記爲 「big」,但只有 10 MB(對於用 DSL 或者電纜上網的用戶來講,這根本不算什麼)。下載這個文件,並展開(如圖 4 所示)。

圖 4. Full SVN 截圖(已展開)

這個最大的下載套件含有 Castor 提供的全部東西 —— 包括依賴項!

如今能夠進入 lib/ 目錄了,其中包含大量的 JAR 文件。這些正是 Castor 所需的庫。

存放更多庫的位置

建立一個新目錄 — 在最初的 Castor 安裝目錄中或者與之同級的目錄 — 而後將剛纔下載的全部 JAR 文件移動到這個目錄中。例如:

[bmclaugh:~] cd /usr/local/java
[bmclaugh:/usr/local/java] ls
castor-1.1.2.1  xalan-j_2_7_0
[bmclaugh:/usr/local/java] cd castor-1.1.2.1/
[bmclaugh:/usr/local/java/castor-1.1.2.1] ls
CHANGELOG                       castor-1.1.2.1.jar
castor-1.1.2.1-anttasks.jar     doc
castor-1.1.2.1-codegen.jar      jdbc-se2.0.jar
castor-1.1.2.1-ddlgen.jar       jta1.0.1.jar
castor-1.1.2.1-jdo.jar          schema
castor-1.1.2.1-xml.jar
[bmclaugh:/usr/local/java/castor-1.1.2.1] mkdir lib
[bmclaugh:/usr/local/java/castor-1.1.2.1] cd lib
[bmclaugh:/usr/local/java/castor-1.1.2.1/lib] cp ~/downloads/castor-1.1.2.1/lib/*.jar .
[bmclaugh:/usr/local/java/castor-1.1.2.1/lib]

此處,我在 Castor 文件夾中建立了一個 lib/ 目錄,將全部的 JAR 文件移到其中,供 Castor 使用。

 

設置類路徑

如今須要設置類路徑中的全部東西。我在 Mac OS X 配置中使用一個 .profile 文件處理全部這些問題。您也許想將您的 profile 也設置爲這樣,或者在 Windows 中設置一個系統環境變量。在任何狀況下,都須要將以下 JAR 文件添加到類路徑:

  • castor-1.1.2.1.jar(在 Castor 主目錄中)
  • castor-1.1.2.1-xml.jar(在 Castor 主目錄中)
  • xerces-J-1.4.0.jar(放在與 Castor 依賴項庫相同的位置)
  • commons-logging-1.1.jar(放在與 Castor 依賴項庫相同的位置)

做爲參考,如下是個人 .profile,從中能夠看到我是如何設置的:

export JAVA_BASE=/usr/local/java
export JAVA_HOME=/Library/Java/Home
export XERCES_HOME=$JAVA_BASE/xerces-2_6_2
export XALAN_HOME=$JAVA_BASE/xalan-j_2_7_0
export CASTOR_HOME=$JAVA_BASE/castor-1.1.2.1
export EDITOR=vi

export CASTOR_CLASSES=$CASTOR_HOME/castor-1.1.2.1.jar:
                      $CASTOR_HOME/castor-1.1.2.1-xml.jar:
                      $CASTOR_HOME/lib/xerces-J_1.4.0.jar:
                      $CASTOR_HOME/lib/commons-logging-1.1.jar



export CVS_RSH=ssh

export PS1="[`whoami`:\w] "



export CLASSPATH=$XALAN_HOME/xalan.jar:$XALAN_HOME/xml-apis.jar:
                 $XALAN_HOME/xercesImpl.jar:

                 ~/lib/mclaughlin-xml.jar:$CASTOR_CLASSES:.

請確保將全部這些文件都放到了類路徑中,接下來將作一個快速測試。

 

測試安裝

首先構建一個很是 簡單的類,而後構建一個實用程序將其在 XML 和 Java 之間來回轉換。這裏並不會演示 Castor 的全部功能,而只是一個很是基本的測試。清單 1 顯示了我將使用的一個 CD 類。輸入這些源代碼並保存爲 CD.java(或者從 參考資料 下載這些代碼)。

清單 1. CD 類(用於測試)
package ibm.xml.castor;

import java.util.ArrayList;
import java.util.List;

/** A class to represent CDs */
public class CD implements java.io.Serializable {

  /** The name of the CD */
  private String name = null;

  /** The artist of the CD */
  private String artist = null;

  /** Track listings */
  private List tracks = null;

  /** Required no-args constructor */
  public CD() {
    super();
  }

  /** Create a new CD */
  public CD(String name, String artist) {
    super();
    this.name = name;
    this.artist = artist;
  }

  public void setName(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

  public void setArtist(String artist) {
    this.artist = artist;
  }

  public String getArtist() {
    return artist;
  }

  public void setTracks(List tracks) {
    this.tracks = tracks;
  }

  public List getTracks() {
    return tracks;
  }

  public void addTrack(String trackName) {
    if (tracks == null) {
      tracks = new ArrayList();
    }
    tracks.add(trackName);
  }
}

如今須要一個類處理編組。如清單 2 所示。

清單 2. 用於測試編組的類
package ibm.xml.castor;

import java.io.FileWriter;

import org.exolab.castor.xml.Marshaller;

public class MarshalTester {

  public static void main(String[] args) {
    try {
      CD sessions = new CD("Sessions for Robert J", "Eric Clapton");
      sessions.addTrack("Little Queen of Spades");
      sessions.addTrack("Terraplane Blues");

      FileWriter writer = new FileWriter("cds.xml");
      Marshaller.marshal(sessions, writer);
    } catch (Exception e) {
      System.err.println(e.getMessage());
      e.printStackTrace(System.err);
    }
  }
}

最後一個類如清單 3 所示,是一個用於解組的類。

清單 3. 用於測試解組的類
package ibm.xml.castor;

import java.io.FileReader;
import java.util.Iterator;
import java.util.List;

import org.exolab.castor.xml.Unmarshaller;

public class UnmarshalTester {

  public static void main(String[] args) {
    try {
      FileReader reader = new FileReader("cds.xml");
      CD cd = (CD)Unmarshaller.unmarshal(CD.class, reader);
      System.out.println("CD title: " + cd.getName());
      System.out.println("CD artist: " + cd.getArtist());
      List tracks = cd.getTracks();
      if (tracks == null) {
        System.out.println("No tracks.");
      } else {
        for (Iterator i = tracks.iterator(); i.hasNext(); ) {
          System.out.println("Track: " + i.next());
        }
      }
    } catch (Exception e) {
      System.err.println(e.getMessage());
      e.printStackTrace(System.err);
    }
  }
}

像下面這樣編譯這些類:

[bmclaugh:~/Documents/developerworks/castor] javac -d . *.java
Note: CD.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

若是使用的是 Java 5 或更高的版本,而在 CD.java 中沒有使用參數化的類型,就會出現上面的警告。不要擔憂,這不會有什麼影響。如今須要運行編組程序測試類。

[bmclaugh:~/Documents/developerworks/castor] java ibm.xml.castor.MarshalTester

找到並打開 cds.xml。其內容應該像這樣:

<?xml version="1.0" encoding="UTF-8"?>
<CD>
  <artist>Eric Clapton</artist>
  <name>Sessions for Robert J</name>
  <tracks xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:type="java:java.lang.String">Little Queen of Spades</tracks>
  <tracks xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:type="java:java.lang.String">Terraplane Blues</tracks>
</CD>

這些內容可讀性不太好,可是應該能在 XML 文檔中看到在 MarshalTester 類中建立的 CD 的全部信息。

如今須要確保能夠將 XML 文檔轉換回 Java。運行解組測試程序:

[bmclaugh:~/Documents/developerworks/castor] java ibm.xml.castor.UnmarshalTester 
CD title: Sessions for Robert J
CD artist: Eric Clapton
Track: Little Queen of Spades
Track: Terraplane Blues

這些代碼很是簡單,無需解釋。若是你得到了這樣的輸出,就說明已經用 Castor 打開了 XML 文件,而且已經將其轉換爲一個 Java 類。

如今 — 咱們假設您已經用 UnmarshalTester 得到了相同的 cds.xml 和輸出 — 您安裝的 Castor 可以正常運行。您尚未使用 Castor 作任何比較複雜的工做,只是確保了它能正常運行、正確設置了 JAR 文件以及 Castor 可以根據須要實現不一樣的功能。

 

結束語

如今,您有了一個可以正常運行的 Castor 環境。清單 12 和 3 讓您對 Castor 的工做原理有了必定的瞭解。看看本身可否實現編組和解組功能吧。在下一篇文章中,您將會更進一步瞭解 Castor 的 XML 特性,以及如何使用映像文件。到那時,請繼續體驗 Castor 的功能。

原文:http://www.ibm.com/developerworks/cn/xml/x-xjavacastor1/index.html

第 2 部分: 編組和解組 XML

須要準備什麼

開始以前須要保證具有了本文所須要的前提條件。確保知足這些要求最簡單的辦法就是按照 本系列第一篇文章(連接參見本文 參考資料 部分)介紹的步驟操做。第一篇文章介紹瞭如何下載、安裝和配置 Castor,並用一些簡單的類進行了測試。

從您手頭的項目中選擇一些類轉換成 XML 而後再轉換回來也沒有問題。本文(以及上一期文章)提供了一些例子,可是要掌握 Castor,最好的辦法是把這裏學到的東西應用到您本身的項目中。首先從一些簡單的對象類開始,好比表明人、唱片、圖書或者某種其餘具體對象的類。而後能夠閱讀本文中關於映射的內容,從而增長一些更復雜的類。

 

編組 101

Castor 最基本的操做是取一個 Java 類而後將類的實例編組成 XML。能夠把類自己做爲頂層容器元素。好比 Book 類極可能在 XML 文檔中獲得一個名爲 「book」 的根元素。

類的每一個屬性也表示在 XML 文檔中。所以值爲 「Power Play」 的 title 屬性將生成這樣的 XML:

<title>Power Play</title>

很容易由此推出一個簡單 Java 類所生成的 XML 文檔的其他部分。

 

編組類的實例

開始編組代碼以前有幾點須要注意。第一,只能編組類的實例而不能編組類自己。類是一種結構,等同於 XML 約束模型,如 DTD 或 XML Schema。類自己沒有數據,僅僅定義了所存儲的數據的結構和訪問方法。

實例化類(或者經過工廠以及其餘實例生成機制得到)將賦予它具體的形式。而後用實際數據填充實例的字段。實例是唯一的,它和同一類的實例具備相同的結構,但數據是不一樣的。圖 1 直觀地說明了這種關係。

圖 1. 類提供結構,實例即數據

類提供數據形式,實例填充數據

於是編組的只能是實例。後面將看到如何使用約束模型和映射文件改變 XML 的結構。可是如今要作的是讓 XML 結構(元素和屬性)和 Java 結構(屬性)匹配。

基本的編組

清單 1 是本文中將使用的一個簡單的 Book 類。

清單 1. Book 類
package ibm.xml.castor;

public class Book {

 /** The book's ISBN */
 private String isbn;
 /** The book's title */
 private String title;
 /** The author's name */
 private String authorName;

 public Book(String isbn, String title, String authorName) {
 this.isbn = isbn;
 this.title = title;
 this.authorName = authorName;
 }

 public String getIsbn() {
 return isbn;
 }

 public void setTitle(String title) {
 this.title = title;
 }

 public String getTitle() {
 return title;
 }

 public void setAuthorName(String authorName) {
 this.authorName = authorName;
 }

 public String getAuthorName() {
 return authorName;
 }
}

編譯上述代碼將獲得 Book.class 文件。這個類很是簡單,只有三個屬性:ISBN、標題和做者姓名(這裏有些問題,暫時先無論)。僅僅這一個文件還不夠,Castor 還須要幾行代碼將 Book 類的實例轉化成 XML 文檔。清單 2 中的小程序建立了一個新的 Book 實例並使用 Castor 轉化成 XML。

清單 2. Book 編組器類
package ibm.xml.castor;

import java.io.FileWriter;

import org.exolab.castor.xml.Marshaller;

public class BookMarshaller {

 public static void main(String[] args) {
 try {
 Book book = new Book("9780312347482", "Power Play", "Joseph Finder");
 FileWriter writer = new FileWriter("book.xml");
 Marshaller.marshal(book, writer);
 } catch (Exception e) {
 System.err.println(e.getMessage());
 e.printStackTrace(System.err);
 }
 }
}

編譯而後運行該程序。將獲得一個新文件 book.xml。打開該文件將看到以下所示的內容:

清單 3. 編譯後的程序建立的 XML
<?xml version="1.0" encoding="UTF-8"?>
<book><author-name>Joseph Finder</author-name>
<isbn>9780312347482</isbn><title>Power Play</title></book>

爲了清晰起見我增長了斷行。實際生成的 XML 文檔在 author-name 元素的結束標記和 isbn 元素的開始標記之間沒有斷行。

Castor 遺漏了什麼

在改進這個例子以前 — 頗有必要改進— 先看看 Castor 在 XML 中漏掉 了什麼:

  • Java 類的包。Java 包不屬於類結構。這其實是一個語義問題,和 Java 名稱空間有關。所以能夠將這個 XML 文檔解組 — 從 XML 轉換爲 Java 代碼 — 到任何具備相同的三個屬性的 Book 實例,不管是什麼包。
  • 字段順序。這是 XML 的順序問題,和 Java 編程無關。所以儘管源文件按某種順序列出字段,但 XML 文檔可能徹底不一樣。這對於 XML 來講相當重要,可是和 Book 類聲明無關。
  • 方法。和包聲明同樣,方法也和數據結構無關。所以 XML 文檔沒有涉及到這些方面,將其忽略了。

那麼就要問 「那又怎麼樣呢?」 若是不重要,XML 忽略這些細節又有什麼關係呢?可是這些信息 重要。之因此重要是由於它們提供了超出預想的更大的靈活性。能夠將這些 XML 解組爲知足最低要求的任何類:

  1. 類名爲 「Book」(使用映射文件能夠改變,但這一點後面再說)。
  2. 這個類包括三個字段:authorNametitle 和 isbn

僅此而已!看看可否編寫知足這些要求的幾個類,並改變類的其餘 字段或者包聲明、方法……很快您就會發現這種方法使得 Castor 多麼靈活。

 

添加更復雜的類型

Book 類最明顯的不足是不能存儲多個做者。修改類使其能處理多個做者也很容易:

清單 4. 存儲多個做者的 Book 類
package ibm.xml.castor;

import java.util.LinkedList;
import java.util.List;

public class Book {

 /** The book's ISBN */
 private String isbn;
 /** The book's title */
 private String title;
 /** The authors' names */
 private List authorNames;

 public Book(String isbn, String title, List authorNames) {
 this.isbn = isbn;
 this.title = title;
 this.authorNames = authorNames;
 }

 public Book(String isbn, String title, String authorName) {
 this.isbn = isbn;
 this.title = title;
 this.authorNames = new LinkedList();
 authorNames.add(authorName);
 }

 public String getIsbn() {
 return isbn;
 }

 public void setTitle(String title) {
 this.title = title;
 }

 public String getTitle() {
 return title;
 }

 public void setAuthorNames(List authorNames) {
 this.authorNames = authorNames;
 }

 public List getAuthorNames() {
 return authorNames;
 }

 public void addAuthorName(String authorName) {
 authorNames.add(authorName);
 }
}

若是使用 Java 5 或 6 技術,將會顯示一些未檢查/不安全操做的錯誤,由於這段代碼沒有使用參數化的 Lists。若是願意能夠本身增長這些代碼。

這樣的修改很簡單,不須要對編組代碼做任何修改。可是,有必要用一本多人編寫的圖書來驗證 Castor 編組器可否處理集合。修改BookMarshaller 類以下:

清單 5. 處理收集器的 Book 類
package ibm.xml.castor;

import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;

import org.exolab.castor.xml.Marshaller;

public class BookMarshaller {

 public static void main(String[] args) {
 try {
 Book book = new Book("9780312347482", "Power Play", "Joseph Finder");
 FileWriter writer = new FileWriter("book.xml");
 Marshaller.marshal(book, writer);

 List book2Authors = new ArrayList();
 book2Authors.add("Douglas Preston");
 book2Authors.add("Lincoln Child");
 Book book2 = new Book("9780446618502", "The Book of the Dead",
 book2Authors);
 writer = new FileWriter("book2.xml");
 Marshaller.marshal(book2, writer);

 } catch (Exception e) {
 System.err.println(e.getMessage());
 e.printStackTrace(System.err);
 }
 }
}

第一本書的處理方式沒有變,從新打開 book.xml 將看到和原來相同的結果。打開 book2.xml 看看 Castor 如何處理集合:

清單 6. 帶有收集器的 XML 結果
<?xml version="1.0" encoding="UTF-8"?>
<book><isbn>9780446618502</isbn><title>The Book of the Dead</title>
<author-names xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:type="java:java.lang.String">Douglas Preston</author-names>
<author-names xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:type="java:java.lang.String">Lincoln Child</author-names>
</book>

顯然,Castor 處理做者姓名列表沒有問題。更重要的是,Castor 不只爲做者名稱建立了一個 blanket 容器,這個框架實際上看到了列表的內部,並認識到內容是字符串(要記住,這不是參數化列表,Castor 必須本身肯定列表成員的類型)。所以 XML 進行了一些很是具體的類型化工做。這是個不錯的特性,尤爲是若是須要將 XML 轉化回 Java 代碼以前進行一些處理的話。

 

添加自定義類

咱們朝着真正實用的程序再前進一步。存儲字符串做者姓名確定最終會出現重複的數據(多數做者寫了不僅一本書)。清單 7 增長了一個新的類 Author

清單 7. Author 類
package ibm.xml.castor;

public class Author {

 private String firstName, lastName;
 private int totalSales;

 public Author(String firstName, String lastName) {
 this.firstName = firstName;
 this.lastName = lastName;
 }

 public String getFirstName() {
 return firstName;
 }

 public String getLastName() {
 return lastName;
 }

 public void setTotalSales(int totalSales) {
 this.totalSales = totalSales;
 }

 public void addToSales(int additionalSales) {
 this.totalSales += additionalSales;
 }

 public int getTotalSales() {
 return totalSales;
 }
}

是否是很簡單?在 Book 類的下面做上述修改以便可以使用新的 Author 類。

清單 8. 使用自定義做者類的 Book 類
package ibm.xml.castor;

import java.util.LinkedList;
import java.util.List;

public class Book {

 /** The book's ISBN */
 private String isbn;
 /** The book's title */
 private String title;
 /** The authors' names */
 private List authors;

 public Book(String isbn, String title, List authors) {
 this.isbn = isbn;
 this.title = title;
 this.authors = authors;
 }

 public Book(String isbn, String title, Author author) {
 this.isbn = isbn;
 this.title = title;
 this.authors = new LinkedList();
 authors.add(author);
 }

 public String getIsbn() {
 return isbn;
 }

 public void setTitle(String title) {
 this.title = title;
 }

 public String getTitle() {
 return title;
 }

 public void setAuthors(List authors) {
 this.authors = authors;
 }

 public List getAuthors() {
 return authors;
 }

 public void addAuthor(Author author) {
 authors.add(author);
 }
}

測試上述代碼須要對 BookMarshaller 略加修改:

清單 9. 增長了做者信息的 BookMarshaller 類
package ibm.xml.castor;

import java.io.FileWriter;
import java.util.ArrayList;
import java.util.List;

import org.exolab.castor.xml.Marshaller;

public class BookMarshaller {

 public static void main(String[] args) {
 try {
 Author finder = new Author("Joseph", "Finder");
 Book book = new Book("9780312347482", "Power Play", finder);
 FileWriter writer = new FileWriter("book.xml");
 Marshaller.marshal(book, writer);

 List book2Authors = new ArrayList();
 book2Authors.add(new Author("Douglas", "Preston"));
 book2Authors.add(new Author("Lincoln", "Child"));
 Book book2 = new Book("9780446618502", "The Book of the Dead",
 book2Authors);
 writer = new FileWriter("book2.xml");
 Marshaller.marshal(book2, writer);

 } catch (Exception e) {
 System.err.println(e.getMessage());
 e.printStackTrace(System.err);
 }
 }
}

僅此而已!編譯而後運行編組器。看看兩個結果文件,這裏僅列出 book2.xml,由於這個文件更有趣一點。

清單 10. 包括做者和圖書信息的 XML 結果
<?xml version="1.0" encoding="UTF-8"?>
<book><authors xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
total-sales="0" xsi:type="java:ibm.xml.castor.Author"><last-name>Preston</last-name>
<first-name>Douglas</first-name></authors><authors 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" total-sales="0"
 xsi:type="java:ibm.xml.castor.Author"><last-name>Child</last-name>
<first-name>Lincoln</first-name></authors><isbn>9780446618502</isbn>
<title>The Book of the Dead</title></book>

Castor 一樣能辦到。它說明了如何同時編組 Book 和 Author 類(再看看 authors 列表)。Castor 甚至爲 Author 增長了 totalSales 屬性,能夠試試看結果如何。

Castor 對泛型的處理

也許聽起來像是全天候的推銷頻道,但 Castor 確實支持泛型和參數化列表。所以若是習慣 Java 5 或 Java 6,能夠這樣修改 Book

清單 11. 處理泛型和參數化列表的 Book 類
package ibm.xml.castor;

import java.util.LinkedList;
import java.util.List;

public class Book {

 /** The book's ISBN */
 private String isbn;
 /** The book's title */
 private String title;
 /** The authors' names */
 private List<Author> authors;

 public Book(String isbn, String title, List<Author> authors) {
 this.isbn = isbn;
 this.title = title;
 this.authors = authors;
 }

 public Book(String isbn, String title, Author author) {
 this.isbn = isbn;
 this.title = title;
 this.authors = new LinkedList<Author>();
 authors.add(author);
 }

 public String getIsbn() {
 return isbn;
 }

 public void setTitle(String title) {
 this.title = title;
 }

 public String getTitle() {
 return title;
 }

 public void setAuthors(List<Author> authors) {
 this.authors = authors;
 }

 public List<Author> getAuthors() {
 return authors;
 }

 public void addAuthor(Author author) {
 authors.add(author);
 }
}

這樣做者列表就 接受 Author 實例了,這是一個很是重要的改進。能夠對 BookMarshaller 做相似的修改,從新編譯,並獲得相同的 XML 輸出。換句話說,能夠爲 Castor 創建很是具體的類。

類變了但 Castor 沒有變

在最終討論解組以前,還須要作一項重要的觀察。前面對類做的全部修改都沒有改變編組代碼!咱們增長了泛型、自定義類、集合等等,可是編組 XML,Castor API 只須要一次簡單的調用。太使人吃驚了!

 

解組

花了不少時間詳細討論完編組之後,解組很是簡單。有了 XML 文檔,並保證具備和數據匹配的 Java 類以後,剩下的工做交給 Castor 就好了。咱們來解組前面生成的兩個 XML 文檔。如清單 12 所示。

清單 12. 解組圖書
package ibm.xml.castor;

import java.io.FileReader;
import java.util.Iterator;
import java.util.List;

import org.exolab.castor.xml.Unmarshaller;

public class BookUnmarshaller {

 public static void main(String[] args) {
 try {
 FileReader reader = new FileReader("book.xml");
 Book book = (Book)Unmarshaller.unmarshal(Book.class, reader);
 System.out.println("Book ISBN: " + book.getIsbn());
 System.out.println("Book Title: " + book.getTitle());
 List authors = book.getAuthors();
 for (Iterator i = authors.iterator(); i.hasNext(); ) {
 Author author = (Author)i.next();
 System.out.println("Author: " + author.getFirstName() + " " +
 author.getLastName());
 }

 System.out.println();

 reader = new FileReader("book2.xml");
 book = (Book)Unmarshaller.unmarshal(Book.class, reader);
 System.out.println("Book ISBN: " + book.getIsbn());
 System.out.println("Book Title: " + book.getTitle());
 authors = book.getAuthors();
 for (Iterator i = authors.iterator(); i.hasNext(); ) {
 Author author = (Author)i.next();
 System.out.println("Author: " + author.getFirstName() + " " +
 author.getLastName());
 }
 } catch (Exception e) {
 System.err.println(e.getMessage());
 e.printStackTrace(System.err);
 }
 }
}

編譯代碼並運行。您可能會獲得意料以外的結果!我遇到的錯誤和堆棧記錄以下所示:

清單 13. 解組遇到的錯誤和堆棧記錄
[bmclaugh:~/Documents/developerworks/castor-2] 
java ibm.xml.castor.BookUnmarshaller
ibm.xml.castor.Book
org.exolab.castor.xml.MarshalException: ibm.xml.castor.Book{File:
 [not available]; line: 2; column: 7}
 at org.exolab.castor.xml.Unmarshaller.
 convertSAXExceptionToMarshalException(Unmarshaller.java:755)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:721)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:610)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:812)
 at ibm.xml.castor.BookUnmarshaller.main 
(BookUnmarshaller.java:14)
Caused by: java.lang.InstantiationException: ibm.xml.castor.Book
 at java.lang.Class.newInstance0(Class.java:335)
 at java.lang.Class.newInstance(Class.java:303)
 at org.exolab.castor.util.DefaultObjectFactory.createInstance(
 DefaultObjectFactory.java:107)
 at org.exolab.castor.xml.UnmarshalHandler.createInstance(
 UnmarshalHandler.java:2489)
 at org.exolab.castor.xml.UnmarshalHandler.startElement(
 UnmarshalHandler.java:1622)
 at org.exolab.castor.xml.UnmarshalHandler.startElement(
 UnmarshalHandler.java:1353)
 at org.apache.xerces.parsers.AbstractSAXParser.startElement 
(Unknown Source)
 at org.apache.xerces.impl.dtd.XMLDTDValidator.startElement 
(Unknown Source)
 at 
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanStartElement(
 Unknown Source)
 at org.apache.xerces.impl.XMLDocumentScannerImpl 
$ContentDispatcher.
 scanRootElementHook(Unknown Source)
 at org.apache.xerces.impl.
 XMLDocumentFragmentScannerImpl 
$FragmentContentDispatcher.dispatch(
 Unknown Source)
 at 
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(
 Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse 
(Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse 
(Unknown Source)
 at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
 at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown 
Source)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:709)
 ... 3 more
Caused by: java.lang.InstantiationException: ibm.xml.castor.Book
 at java.lang.Class.newInstance0(Class.java:335)
 at java.lang.Class.newInstance(Class.java:303)
 at org.exolab.castor.util.DefaultObjectFactory.createInstance(
 DefaultObjectFactory.java:107)
 at org.exolab.castor.xml.UnmarshalHandler.createInstance(
 UnmarshalHandler.java:2489)
 at org.exolab.castor.xml.UnmarshalHandler.startElement 
(UnmarshalHandler.java:1622)
 at org.exolab.castor.xml.UnmarshalHandler.startElement 
(UnmarshalHandler.java:1353)
 at org.apache.xerces.parsers.AbstractSAXParser.startElement 
(Unknown Source)
 at org.apache.xerces.impl.dtd.XMLDTDValidator.startElement 
(Unknown Source)
 at 
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanStartElement(
 Unknown Source)
 at org.apache.xerces.impl.XMLDocumentScannerImpl 
$ContentDispatcher.
 scanRootElementHook(Unknown Source)
 at org.apache.xerces.impl.
 XMLDocumentFragmentScannerImpl 
$FragmentContentDispatcher.dispatch(
 Unknown Source)
 at 
org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(
 Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse 
(Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse 
(Unknown Source)
 at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
 at org.apache.xerces.parsers.AbstractSAXParser.parse(Unknown 
Source)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:709)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:610)
 at org.exolab.castor.xml.Unmarshaller.unmarshal 
(Unmarshaller.java:812)
 at ibm.xml.castor.BookUnmarshaller.main 
(BookUnmarshaller.java:14)

那麼究竟是怎麼回事呢?您遇到了直接使用 Castor 進行數據綁定必需要作出的幾個讓步中的第一個。

Castor 要求使用無參數的構造器

Castor 主要經過反射和調用 Class.forName(類名).newInstance() 這樣的方法進行解組,於是 Castor 不須要了解不少就能實例化類。可是,它還要求類必須能經過不帶參數的構造器實例化。

好消息是 Book 和 Author 類只需簡單修改就能避免這些錯誤。只須要爲兩個類增長不帶參數的構造器,public Book() { } 和 public Author() { }。從新編譯並運行代碼。

想一想這意味着什麼。有了無參數的構造器,不只僅是 Castor,任何 類或者程序都能建立類的新實例。對於圖書和做者來講,能夠建立沒有 ISBN、標題和做者的圖書,或者沒有姓和名的做者 — 這確定會形成問題。若是 Castor 沒有更先進的特性(下一期再討論)這種缺陷就不可避免,所以必須很是注意類的使用。還必須考慮爲任何可能形成 NullPointerException 錯誤的數據類型設置具體的值(字符串能夠設爲空串或者默認值,對象必須初始化等等)。

第二步:null 引發的更多問題

爲 Book 和 Author 類增長無參數構造器,從新運行解組器。結果以下:

清單 14. 添加無參數構造器後的結果
[bmclaugh:~/Documents/developerworks/castor-2] 
java ibm.xml.castor.BookUnmarshaller
Book ISBN: null
Book Title: Power Play
Author: null null

Book ISBN: null
Book Title: The Book of the Dead
Author: null null
Author: null null

看來還有問題沒解決(不是說解組很簡單嗎?先等等,後面還要說)。值得注意的是,有幾個字段爲空。把這些字段和 XML 比較,會發現這些字段在 XML 文檔都有正確的表示。那麼是什麼緣由呢?

爲了找出根源,首先注意圖書的標題 設置正確。此外仔細觀察第二本書 The Book of the Dead。雖然初看起來彷佛兩本書的做者都沒有設置,但問題不在這裏。事實上,第二本書正確引用了兩個 Author 對象。所以它引用了這兩個對象,可是這些對象自己沒有設置姓和名。所以圖書的標題設置了,做者也同樣。可是 ISBN 是空的,做者的姓和名也是空的。這是另外一條線索:若是顯示做者圖書的總銷售量,這些值就不會 是空。

發現了嗎?空值字段都沒有 setter(或 mutator)方法。沒有 setIsbn()setFirstName() 等等。這是由於類被設計成須要構造信息(一旦書有了本身的 ISBN,這個 ISBN 實際上就不能改變了,基本上就意味着一本書造成了,所以最好要求構造/實例化新圖書的時候提供新的 ISBN)。

還記得嗎?Castor 使用反射機制。偏偏由於只有對象提供無參數構造器的時候它才能建立對象,由於沒有 setFieldName() 方法就沒法設置字段的值。所以須要爲 Book 和 Author 類增長一些方法。下面是須要添加的方法(實現很簡單,留給讀者本身完成):

  • setIsbn(String isbn)Book 類)
  • setFirstName(String firstName)Author 類)
  • setLastName(String lastName)Author 類)

增長上述方法以後從新編譯就好了。

第三步:成功解組

再次嘗試運行解組器,將獲得以下所示的結果:

清單 15. 增長做者和 ISBN setter 方法後的解組結果
[bmclaugh:~/Documents/developerworks/castor-2] 
java ibm.xml.castor.BookUnmarshaller
Book ISBN: 9780312347482
Book Title: Power Play
Author: Joseph Finder

Book ISBN: 9780446618502
Book Title: The Book of the Dead
Author: Douglas Preston
Author: Lincoln Child

終於獲得了咱們須要的結果。必須對類做幾方面的修改。可是前面我曾提到解組很是簡單。可是這和上面的狀況不符,解組不是須要對類做不少修改嗎?

實際上的確很簡單。修改都是在您的類中,而和 Castor 解組過程無關。Castor 用起來很是簡單,只要類的結構符合 Castor 的要求 — 每一個類都要有無參數構造器,每一個字段都要有 get/set 方法。

 

代價是值得的

如今的類和最初相比沒有很大變化。Book 和 Author 沒有面目全非,功能上沒有很大不一樣(實際上沒有什麼區別)。可是如今設計還有一點問題。圖書必須 有 ISBN(至少在我看來),所以可以建立沒有 ISBN 的圖書 — 無參數構造器 — 令我感到困擾。此外,我也不肯意別人隨便改動圖書的 ISBN,這樣不能正確地表示圖書對象。

做者的修改不那麼嚴重,由於對於做者這個對象來講姓名可能不是很重要的標識符。名字能夠改,如今甚至姓均可以改。另外,兩位做者若是同名,目前也沒有區分的現成辦法。但即便增長了更具惟一性、更合適的標識符(社會安全號碼、駕駛證號、其餘標識符),仍然須要無參數構造器和字段 setter 方法。所以問題仍然存在。

這就變成了效益和控制的問題:

  1. Castor 是否提供了足夠的好處 — 簡化數據綁定 — 足以抵得上設計所要作出的讓步?
  2. 對代碼庫是否有足夠的控制權,避免濫用爲 Castor 增長的方法?

只有您才能表明您的企業回答這些問題。對於多數開發人員來講,權衡的結果傾向於 Castor,就必須作出適當的讓步。對於那些沉醉於設計或者因爲特殊需求沒法作出這些讓步的少數人,可能更願意本身編寫簡單的 XML 序列化工具。不管如何,應該將 Castor 放到您的工具包裏。若是它不適合當前的項目,也許下一個用得上。

 

結束語

到目前爲止尚未提到的 Castor 的一大特性是映射文件。咱們是直接從 Java 代碼翻譯成 XML。可是有幾個假定:

  1. 須要把 Java 類中的全部字段持久到 XML。
  2. 須要在 XML 中保留類名和字段名。
  3. 有一個類模型和須要反序列化的 XML 文檔中的數據匹配。

對於企業級編程,這些假定都有可能不成立。若是將字段名和類名存儲到 XML 文檔中被認爲是一種安全風險,可能會過多泄露應用程序的結構,該怎麼辦?若是交給您一個 XML 文檔須要轉化成 Java 代碼,但但願使用不一樣的方法名稱和字段名稱,怎麼辦?若是隻須要少數類屬性存儲到 XML 中,怎麼辦?

全部這些問題的答案都是一個:Castor 提供了進一步控制 Java 代碼和 XML 之間映射關係的方式。下一期文章咱們將詳細介紹映射。如今,不要只考慮編組和解組,還要花時間想一想爲了讓 Castor 正常工做所做的那些修改意味着什麼。在思考的同時,不要忘記回來看看下一期文章。到時候再見。

原文:http://www.ibm.com/developerworks/cn/xml/x-xjavacastor2/index.html

第 3 部分: 模式之間的映射

您如今應該具有的(回顧)

與前一篇文章同樣,本文也對您系統的設置狀況和您的技能作一些假設。首先,須要按照本系列的第 1 部分中的描述下載並安裝 Castor 的最新版本,設置類路徑和相關的 Java 庫(參見 參考資料 中本系列第一篇文章的連接)。而後,按照第 2 部分中的描述,熟悉 Castor 的基本編組和解組設施。

因此,您應該可以使用 Castor 提取出 XML 文檔中的數據,並使用本身的 Java 類處理數據。用數據綁定術語來講,這稱爲解組(unmarshalling)。反向的過程稱爲編組(marshalling):您應該可以把 Java 類的成員變量中存儲的數據轉換爲 XML 文檔。若是您還不熟悉 Castor 的解組器和編組器,那麼應該閱讀 第 2 部分(參見 參考資料 中的連接)。

 

非理想環境下的數據綁定

初看上去,您彷佛已經掌握了有效地使用 Castor 所需瞭解的全部過程:設置、編組和解組。可是,您到目前爲止學到的全部東西只適用於所謂的理想環境。在這樣的環境中,每一個人編寫的 XML 都是完美的,其中的元素名是有意義的,好比 「title」 和 「authorAddress」,而不是 「t」 或 「aa」。Java 類是按照有組織的方式建立的,採用單數做爲類名(好比 「Book」),採用單數名詞做爲成員變量名(好比 「isbn」 和 「price」)。另外,數據類型也是正確的:沒有開發人員把 price 的數據類型設置爲 int 而不是 float,或者使用 char 數組存儲字符串數據(這是 C 語言的作法)。

可是,大多數程序員所處的環境並不完美(我真想找到一個可以把我送到完美世界的魔法衣廚)。在大多數程序員所處的環境中有許多不理想的狀況:XML 文檔經常有糟糕的元素名和屬性名,還要應付名稱空間問題。元素數據存儲在屬性中,一些數據甚至由管道符或分號分隔。

Java 類是繼承的,對它們進行從新組織在時間和工做量方面的成本可能會超過帶來的好處。這些類經常沒法簡潔地映射到 XML 模式(認爲 XML 和數據人員會與 Java 程序員相互妥協的想法也是很是難以想象的),並且在某些狀況下,即便實現了簡潔映射,也確定不會跨全部類和數據。XML 元素名可能不合適,許多 Java 變量名也可能不合適。甚至可能遇到使用 Hungarian 表示法的名稱,按照這種表示法,全部成員變量都以 「m」 開頭,好比 mTitle。這很很差看。

在這些狀況下,您目前學到的數據綁定方法就無能爲力了。XML 文檔中可能會出現 Hungarian 風格的元素名,Java 類中也可能出現沒有意義的結構。這種狀況是沒法接受的。若是不能按照您但願的方式獲取和操做 XML 文檔的數據,那麼 Castor(或任何數據綁定框架)又有什麼意義呢?

 

靈活數據綁定的目標

首先要注意,在 Castor 或任何其餘數據綁定框架中,使用映射文件都要花一些時間。必須先學習一些新語法。儘管映射文件使用 XML 格式(大多數框架都是這樣的),可是您須要學習一些新元素和屬性。還必須作一些測試,確保 XML 和 Java 代碼之間的相互轉換產生您但願的結果。最後,若是親自指定映射,而不是讓框架處理映射,就可能在數據綁定中遇到更多的錯誤。例如,若是但願讓框架把 XML 中的 fiddler 元素映射到 Java 代碼中的 violin 屬性,可是錯誤地聲明這個屬性是在 player 類中(應該是在 Player 類中),那麼就會遇到錯誤。所以,在親自指定映射時,必須很是注意拼寫、大小寫、下劃線、單引號和雙引號。

在學習使用映射文件以前,應該肯定確實須要這麼作。若是掌握了映射文件,可是卻不使用它,那就是浪費時間。可是,映射確實有一些優勢。

Java 代碼再也不受 XML 命名方式的限制

前面曾經提到,在把 XML 轉換爲 Java 代碼時,大小寫可能會致使錯誤。在 XML 中,最經常使用的作法是名稱所有小寫並加連字符,好比 first-name。有時候,甚至會看到 first_name。這樣的名稱會轉換爲很難看的 Java 屬性名;沒人願意在代碼中調用 getFirst-name()。實際上,在大多數由程序員(而不是 XML 開發人員或數據管理員)編寫的文檔中,每每使用駝峯式(camel-case)命名法,好比 firstName。經過使用映射文件,很容易把 XML 風格的名稱(好比 first-name)映射爲 Java 風格的名稱(好比 firstName)。最棒的一點是,不須要強迫 XML 人員像 Java 程序員那樣思考,這每每比學習新的映射語法困可貴多。

XML 再也不受 Java 命名方式的限制

是的,這彷佛很明顯。既然能夠調整 XML 到 Java 的命名轉換,反過來確定也能夠:在把 Java 類和屬性包含的數據轉換爲 XML 時,能夠修改 Java 名稱。可是,有一個更重要,也更微妙的好處:再也不受到 Java 類名和包名的限制。

這極可能成爲一個組織問題。例如,在大多數狀況下,XML 中的嵌套元素轉換爲類結構,最內層的嵌套元素轉換成類屬性(成員變量)。看一下清單 1 中的 XML:

清單 1. 表明圖書的 XML
<?xml version="1.0" encoding="UTF-8"?>
<book>
 <authors total-sales="0">
  <last-name>Finder</last-name>
  <first-name>Joseph</first-name>
 </authors>
 <isbn>9780312347482</isbn>
 <title>Power Play</title>
</book>

Castor(或任何其餘數據綁定框架)可能假設您須要一個 Book 類,這個類引用幾個 Author 類實例。author 類應該有成員變量 lastName 和firstName(這裏會出現前面提到的命名問題,Author 中的成員變量應該是 last-name,仍是 lastName?對於名字也有這個問題)。可是,若是這不是您但願的結果,應該怎麼辦?例如,您可能在一個稱爲 Person 或 Professional 的類中存儲全部做家、會議演講人和教授。在這種狀況下就真有麻煩了,並且您不會願意全面修改 XML 元素的結構和名稱來解決這個問題。實際上,在這種狀況下,要想原樣保持 XML,使用映射是唯一的辦法。

映射容許咱們在 Java-XML 轉換的兩端指定命名方式。咱們不但願因爲 XML 文檔的緣由修改 Java 代碼,一樣不肯意修改 XML 結構來適應 Java 類和成員變量。另外,Java 包也會增長複雜性。儘管在 Castor 中包並非大問題,可是仍然必須在編組的 XML 中存儲 Java 類和包的相關信息,這對於業務邏輯(Java 類)和數據(XML)的隔離很不利。映射能夠解決全部這些問題。

映射容許在現有環境中添加數據綁定

前兩個問題(對 XML 和 Java 代碼的限制)實際上與一個更大的問題相關。大多數狀況下,您已經有了一組 Java 對象和一個或多個 XML 文檔。所以,不具有前兩篇文章中的那種自由度:不能讓 Castor 根據它本身的規則把 Java 代碼解組爲 XML,或者爲 XML 文檔生成 Java 類。

相反,更爲常見的狀況是,您須要把一種新技術 — 數據綁定 — 添加到現有的結構中。在這種狀況下,映射文件就是使用數據綁定的關鍵。在兩個 「端點」(當前的對象模型和當前的 XML 結構)固定的狀況下,映射使咱們仍然可以把這二者的數據聯繫起來。簡而言之,良好的映射系統使數據綁定可以在真實環境中發揮做用,而不只僅停留在理論上。

 

一個映射場景示例

咱們先來看一個簡單的映射場景。在前一篇文章中,咱們開發了 Book 和 Author 類。清單 2 是 Book 類。

清單 2. Book 類
package ibm.xml.castor;

import java.util.LinkedList;
import java.util.List;

public class Book {

  /** The book's ISBN */
  private String isbn;
  /** The book's title */
  private String title;
  /** The authors' names */
  private List<Author> authors;

  public Book() { }

  public Book(String isbn, String title, List<Author> authors) {
    this.isbn = isbn;
    this.title = title;
    this.authors = authors;
  }

  public Book(String isbn, String title, Author author) {
    this.isbn = isbn;
    this.title = title;
    this.authors = new LinkedList<Author>();
    authors.add(author);
  }

  public void setIsbn(String isbn) {
    this.isbn = isbn;
  }

  public String getIsbn() {
    return isbn;
  }

  public void setTitle(String title) {
    this.title = title;
  }

  public String getTitle() {
    return title;
  }

  public void setAuthors(List<Author> authors) {
    this.authors = authors;
  }

  public List<Author> getAuthors() {
    return authors;
  }

  public void addAuthor(Author author) {
    authors.add(author);
  }
}

清單 3 是 Author 類。

清單 3. Author 類
package ibm.xml.castor;

public class Author {

  private String firstName, lastName;

  public Author() { }

  public Author(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public String getFirstName() {
    return firstName;
  }
  
  public String getLastName() {
    return lastName;
  }

}

注意:與前一篇文章相比,唯一的修改是在 Author 中刪除了總銷售額(totalSales 變量)。我發現它的意義不大,因此在這個版本中刪除了它。

一個比較麻煩的 XML 文檔

這一次不使用前一篇文章中的 XML,而是使用一個不太容易映射的 XML 文檔。清單 4 給出但願綁定到 清單 2 和 清單 3 中的 Java 類的 XML 文檔。

清單 4. 用於數據綁定的 XML
<?xml version="1.0" encoding="UTF-8"?>
<book>
 <author>
  <name first="Douglas" last="Preston" />
 </author>

 <author>
  <name first="Lincoln" last="Child" />
 </author>

 <book-info>
  <isbn>9780446618502</isbn>
  <title>The Book of the Dead</title>
 </book-info>
</book>

須要映射哪些數據?

在處理映射文件語法或 Castor 的 API 以前,第一個任務是判斷須要把 XML(清單 4)中的哪些數據綁定到 Java 類(清單 2 和 清單 3)。請考慮一下子。

下面簡要總結一下這個 XML 文檔應該如何映射到 Java 類:

  • book 元素應該映射到 Book 類的一個實例。
  • 每一個 author 元素應該映射到 Author 類的一個實例。
  • 每一個 Author 實例應該添加到 Book 實例中的 authors 列表中。
  • 對於每一個 Author 實例,firstName 應該設置爲 name 元素上的 first 屬性的值。
  • 對於每一個 Author 實例,lastName 應該設置爲 name 元素上的 last 屬性的值。
  • Book 實例的 ISBN 應該設置爲 book-info 元素中嵌套的 isbn 元素的值。
  • Book 實例的書名應該設置爲 book-info 元素中嵌套的 title 元素的值。

其中一些映射您已經知道如何實現了。例如,book 到 Book 類實例的映射是標準的,Castor 會默認處理這個任務。可是,也有一些新東西,好比說做者。儘管把一個 author 元素映射到一個 Author 實例沒什麼問題,可是沒有分組元素,好比 authors,它清楚地顯示出全部做者屬於Book 實例中的一個集合。

這裏還有一些元素和屬性並不直接映射到 Java 對象模型。Author 類包含表示名字 姓氏的變量,可是在 XML 中只用一個元素(name)表示做者姓名,這個元素有兩個屬性。book-info 元素中嵌套的書名和 ISBN 並不映射到任何 Java 對象。

這種狀況很是適合使用映射文件。它使咱們可以使用這種 XML(包含咱們須要的數據,可是結構不符合但願),仍然可以把文檔中的數據放到 Java 對象中。並且,映射文件自己並不難編寫。

 

基本的映射文件

Castor 中的映射是經過使用映射文件(mapping file) 實現的。映射文件僅僅是一個 XML 文檔,它提供瞭如何在 Java 代碼和 XML 之間進行轉換的相關信息。由於您熟悉 XML,因此您會發現編寫映射文件是很是容易的。實際上,對於簡單的映射(只需修改元素名和 Java 類或成員變量的名稱),只需一點兒時間就能編寫好映射文件。

而後,當進行編組和解組時(前兩篇文章已經介紹過如何在程序中進行編組和解組),Castor 會使用這個文件。只需對 Castor 多作一個 API 調用;其餘代碼都是同樣的。

mapping 元素

Castor 映射文件的開始是一個普通的 XML 文檔,而後是根元素 mapping。還能夠引用 Castor 映射 DTD。這樣就能夠檢驗文檔,確保結構和語法沒有任何問題。這會大大簡化對映射的調試。

清單 5 給出最基本的映射文件。

清單 5. 基本的 Castor 映射文件
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
                         "http://castor.org/mapping.dtd">

<mapping>
  <!-- All your mappings go here -->
</mapping>

這個文件顯然沒有實質性內容,但它是全部映射文件的起點。

用 class 元素對類進行映射

創建基本的映射文件以後,差很少老是先要把一個 Java 類映射到一個 XML 元素。在這個示例中,須要把 Book 類映射到 book 元素中的數據。映射文件首先考慮類,因此須要添加一個 class 元素,並在這個元素的 name 屬性中指定徹底限定的 Java 類名,好比:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
                         "http://castor.org/mapping.dtd">

<mapping>
  <class name="ibm.xml.castor.Book">
  </class>
</mapping>

如今,可使用 map-to 元素和 xml 屬性指定這個類要映射到的 XML 元素。這個元素嵌套在 XML 元素映射到的 Java 類(徹底限定,包括包名)的 class 元素中,好比:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
                         "http://castor.org/mapping.dtd">

<mapping>
  <class name="ibm.xml.castor.Book">
  <map-to xml="book" />
  </class>
</mapping>

這很是簡單。實際上,到目前爲止,這個映射文件只實現 Castor 的默認操做。除非因爲如下兩個緣由,不然能夠刪除這個部分並讓 Castor 處理這個任務:

  1. 須要指定如何填充 Book 中的某些字段,好比書名和 ISBN。
  2. 若是使用映射文件,那麼最好指定全部內容 的映射方式。這會更明確 XML 和 Java 代碼之間的配合。

把字段映射到元素

有了基本的類到元素的映射以後,就能夠開始把 Book 類的字段映射到 XML 文檔中的特定元素。Castor 映射文件使用 field 元素指定要使用的 Java 成員變量,使用其中嵌套的 bind-xml 元素指定映射到的 XML 元素。所以,下面的代碼指定把 Book 類中的 title 變量映射到 book 元素中嵌套的 title 元素:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
                         "http://castor.org/mapping.dtd">

<mapping>
  <class name="ibm.xml.castor.Book">
    <map-to xml="book" />

    <field name="Title" type="java.lang.String">     <bind-xml name="title" node="element" />    </field>
  </class>
</mapping>

在這裏要注意兩點。首先,提供了屬性名(在這個示例中是 「title」)。這是屬性(property) 名,而不是成員變量名。換句話說,Castor 經過調用 set[PropertyName]() 來使用這個屬性名。若是提供屬性名 「foo」,Castor 就會嘗試調用 setfoo() — 這可能不是您但願的狀況。所以,要仔細注意大小寫,並使用屬性 名而不是 Java 類中的變量名。

要注意的第二點是 type 屬性。這個屬性向 Castor 說明數據到底是什麼類型的。在這個示例中,這很簡單;可是在某些狀況下,但願將以 XML 文本式數據存儲的數字存儲爲整數、小數或者只是字符串,這時指定正確的數據類型就很重要了。另外,類型應該使用徹底限定的 Java 類名,好比 java.lang.String

在 bind-xml 元素中,指定要綁定到的 XML 元素的名稱(在這個示例中是 「title」),並使用 node 屬性指定是綁定到元素仍是綁定到屬性。這樣就能夠輕鬆地使用元素和屬性數據,只需在映射文件中稍作修改便可。

指定 XML 元素的位置

可是,這裏須要解決一個問題:書名和 ISBN 嵌套在 book-info 元素中,而不是直接嵌套在 book 元素中。因此須要在映射文件中指出這一狀況。

當遇到這種狀況時 — 一個類中的一個字段並不直接映射到與這個類對應的 XML 元素中的數據 — 就須要在 bind-xml 元素中使用 location 屬性。這個屬性的值應該是 XML 文檔中包含您但願綁定到的數據的元素。若是綁定到元素數據,它就應該是目標元素的 元素;若是綁定到屬性數據,它就應該是包含這個屬性的 元素。

所以,在這個示例中,但願把 Book 類的書名屬性綁定到 title 元素的值,而這個元素嵌套在 book-info 元素中。下面是映射方法:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN"
                         "http://castor.org/mapping.dtd">

<mapping>
  <class name="ibm.xml.castor.Book">
    <map-to xml="book" />

    <field name="Title" type="java.lang.String">
      <bind-xml name="title" node="element" location="book-info"
    </field>
  </class>
</mapping>

而後爲書的 ISBN 添加另外一個字段映射:

點擊查看代碼清單

如今,Book 類的屬性都已經設置好了。該處理 Author 類了。

 

對其餘類進行映射

按照相同的方式對其餘類進行映射。唯一的差別是在其餘類中不須要使用 map-to 元素。

對 Author 類進行映射

須要把 Author 類中的字段映射到 author 元素。請記住,下面是要處理的 XML 片斷:

<author>
  <name first="Lincoln" last="Child" />
 </author>

唯一須要注意的是,這裏並不用兩個元素分別包含名字和姓氏,而是使用一個帶兩個屬性的元素。可是,前面已經使用過 location 屬性(須要用這個屬性指定 name 元素是映射到的位置)和 node 屬性(能夠在這裏指定要綁定到屬性數據,而不是元素)。因此在映射文件中須要如下代碼:

點擊查看代碼清單

如今,您應該很容易看懂這些代碼。這裏指定了映射到的類(Author)和這個類上要映射的屬性(FirstName 和 LastName)。對於每一個屬性,指定要查看的 XML 元素(都是 name)並指定須要的是屬性數據。

把 Book 和 Author 類連接起來

若是看一下 上面 的 XML,就會注意到並無指定 Author 類應該映射到哪一個 XML 元素。這是一個問題,由於 Castor 不會猜想您的意圖;只要使用映射文件,最好指定全部映射信息。

若是每本書只有一位做者,那麼在 Book 類中可能有一個 Author 屬性。在這種狀況下,能夠在映射文件中插入如下代碼:

<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" 
          "http://castor.org/mapping.dtd">

<mapping>
  <class name="ibm.xml.castor.Book">
    <map-to xml="book" />

    <field name="Title" type="java.lang.String">
      <bind-xml name="title" node="element" location="book-info" />
    </field>
    <field name="Isbn" type="java.lang.String">
      <bind-xml name="isbn" node="element" location="book-info" />
    </field>

    <field name="Author" type="ibm.xml.castor.Author"><bind-xml name="author" /></field>
  </class>

  <class name="ibm.xml.castor.Author">
    <field name="FirstName" type="java.lang.String">
      <bind-xml name="first" node="attribute" location="name" />
    </field>

    <field name="LastName" type="java.lang.String">
      <bind-xml name="last" node="attribute" location="name" />
    </field>
  </class>
</mapping>

在這種狀況下,把這一映射放在映射文件的圖書部分中,由於映射的是屬於 Book 類的一個屬性。映射的類型指定爲ibm.xml.castor.Author,並指定 XML 元素 author。這樣的話,Castor 的映射系統就會使用 class 元素中的 Author 類的定義處理做者的屬性。

可是,問題在於 Book 類中沒有 Author 屬性。相反,這個類中有一個 Authors 屬性,其中包含 Author 實例的集合。所以,必須讓 Castor 把每一個 author 元素映射到一個 Author 實例(這一步差很少已經完成了),而後把全部實例組合成 Book 的 Authors 屬性。

 

把元素映射到集合

爲了映射圖書和做者的關係,須要把幾個元素(XML 文檔中的每一個 author)映射到一個集合,而後把這個集合分配給 Book 的 Authors 屬性。

首先使用 field 元素,由於確實要映射到 Book 的一個字段。還要把 name 屬性的值指定爲 「Authors」,由於這是 Book 中將映射到的屬性:

<field name="Authors">
</field>

接下來,須要提供屬性的類型。您可能認爲這應該是集合類型。可是,實際上但願指定集合中每一個成員的類型。因此類型應該是 ibm.xml.castor.Author。您將會得到 ibm.xml.castor.Author 類的實例,Castor 將把這些實例放到 Authors 屬性中:

<field name="Authors" type="ibm.xml.castor.Author">
</field>

下面是關鍵之處:使用 collection 屬性指定這個屬性是一個集合。這個屬性的值是集合的類型。Castor 當前只支持兩個值:vector(表明列表類型)和 array(表明數組類型)。經過標準的 Java 集合 API(好比 next() 等調用)訪問第一種集合;管理第二種集合的方法與 Java 數組類似,按照索引來訪問它們,好比 ar[2]。在這個示例中,由於 Java 類型是 List,因此使用 vector

<field name="Authors" type="ibm.xml.castor.Author" collection="vector">
</field>

若是指定了 collection 屬性,Castor 就知道應該用與 type 屬性對應的值構建這個集合。所以,這裏的 Authors 屬性應該是ibm.xml.castor.Author 類型的實例的集合。

如今只剩下一步了:指定獲取這些 Author 實例的來源。這要使用 bind-xml 元素:

<field name="Authors" type="ibm.xml.castor.Author" collection="vector">
  <bind-xml name="author" />
</field>

全部工做都完成了;如今造成了一個完整的映射文件。最終的文件應該像清單 6 這樣。

清單 6. 完整的映射文件
<?xml version="1.0"?>
<!DOCTYPE mapping PUBLIC "-//EXOLAB/Castor Mapping DTD Version 1.0//EN" 
          "http://castor.org/mapping.dtd">

<mapping>
  <class name="ibm.xml.castor.Book">
    <map-to xml="book" />

    <field name="Title" type="java.lang.String">
      <bind-xml name="title" node="element" location="book-info" />
    </field>
    <field name="Isbn" type="java.lang.String">
      <bind-xml name="isbn" node="element" location="book-info" />
    </field>

    <field name="Authors" type="ibm.xml.castor.Author" collection="vector">
      <bind-xml name="author" />
    </field>
  </class>

  <class name="ibm.xml.castor.Author">
    <field name="FirstName" type="java.lang.String">
      <bind-xml name="first" node="attribute" location="name" />
    </field>

    <field name="LastName" type="java.lang.String">
      <bind-xml name="last" node="attribute" location="name" />
    </field>
  </class>
</mapping>
 

在程序中使用映射文件

最後,須要在解組過程當中使用這個映射文件。之前,咱們靜態地使用 Unmarshaller 類,經過調用 Unmarshaller.unmarshal() 把 XML 轉換爲 Java 代碼。可是,由於如今要使用映射文件,因此須要建立一個 Unmarshaller 實例並設置一些選項。清單 7 給出的類處理從 XML 文檔到 Java 對象的解組過程。

清單 7. 用映射文件進行解組
package ibm.xml.castor;

import java.io.FileReader;
import java.util.Iterator;
import java.util.List;

import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.xml.Unmarshaller;

public class BookMapUnmarshaller {
 
  public static void main(String[] args) {
    Mapping mapping = new Mapping();

    try {
      mapping.loadMapping("book-mapping.xml");
      FileReader reader = new FileReader("book.xml");
      Unmarshaller unmarshaller = new Unmarshaller(Book.class);unmarshaller.setMapping(mapping);
      Book book = (Book)unmarshaller.unmarshal(reader);
      System.out.println("Book ISBN: " + book.getIsbn());
      System.out.println("Book Title: " + book.getTitle());
      List authors = book.getAuthors();
      for (Iterator i = authors.iterator(); i.hasNext(); ) {
        Author author = (Author)i.next();
        System.out.println("Author: " + author.getFirstName() + " " +
                                        author.getLastName());
      }
    } catch (Exception e) {
      System.err.println(e.getMessage());
      e.printStackTrace(System.err);
    }
  }
}

與前兩篇文章中的解組器相比,這裏的更改很是少。首先,建立一個 Unmarshaller 實例,使用的參數是 Book.class。這告訴解組器要解組的頂級類是哪一個類。注意,這個頂級 Java 類對應於使用 map-to 元素的 mapping 元素。而後設置映射,最後調用 unmarshal() 的非靜態版本。

如今完成了!這個過程與之前的過程差別並不大。做爲練習,您能夠本身試着編寫把 Java 代碼編組爲 XML 的代碼。請參考前一篇文章中的BookMarshaller 類並設置映射文件,而後嘗試在 XML 和 Java 代碼之間來回轉換。

 

結束語

數據綁定最終關注的是數據,而不是存儲數據的格式。對於大多數 Java 程序員來講,處理 Java 對象是很容易的,而經過數據綁定,可以一樣輕鬆地把來自各類來源(尤爲是 XML)的數據轉換爲 Java 對象。另外,數據綁定環境中的映射甚至更進了一步:在填充 Java 對象時,能夠很是靈活地處理數據源格式。所以,若是您喜歡數據綁定,那麼必定也會喜歡映射;它使您可以綁定那些與您須要的命名約定不太相符的 XML 文檔,也可以使用與您的 Java 對象不相符的結構。

對於數據人員,映射會帶來一樣的好處。當調用 Java 方法並保存在命名古怪的 XML 風格的變量中,或者 XML 文檔中有多個元素所有映射到同一個類,那麼不須要構建中間層就能夠從 Java 類中取得所需的數據。最重要的是靈活性,可以對數據作 想作的事情,而不受框架或工具的限制。

 

後續內容

您已經看到了 Castor 在 XML 環境中提供了什麼。可是,這僅僅觸及到了 Castor 的皮毛。在下一篇文章中,將進一步擴展簡單的 XML 數據綁定並研究 Castor 的 SQL 數據綁定設施。咱們將把數據從 Java 類轉移到 SQL 數據庫中,再轉移回來,並且不須要使用 JDBC。請複習一下 XML 和 SQL 知識,下個月咱們將進一步體驗數據綁定的威力。

學完下一篇文章(本系列的最後一篇)以後,您就可以用相同的 API 在 XML、Java 和 SQL 數據庫之間進行轉換。這甚至會帶來比映射文件更大的靈活性。對於全部數據存儲格式,可使用單一 API 和類似的調用處理數據庫、Java 對象和 XML 文檔。實際上,對於那些瞭解 C# 的程序員來講,這聽起來很是 像 LINQ(LINQ 是 Visual C# 2008 中最新最熱門的技術之一)。類似的功能已經用 Java 技術實現了,並且具備一個穩定的 API。很棒,不是嗎?因此請繼續研究 Castor,綁定數據,試試您能實現哪些功能。享受創造的樂趣吧!咱們網上相見。

原文:http://www.ibm.com/developerworks/cn/xml/x-xjavacastor3/index.html

相關文章
相關標籤/搜索