在深入數據綁定的細節,尤其是探討如何將數據綁定工具包用於一般的編程問題之前,您需要選擇使用的數據綁定工具包。我的一般原則是您應該自行挑選軟件,因為每個人的編程需求無疑都是唯一的。也就是說,程序員做出這些決策所用的信息是普遍適用的。本文中,我將根據這些普遍的原則分析 JAXB,並幫助確定 JAXB 是否適合您的數據綁定需求。
簡述
不過在展開 JAXB 的討論之前,我要簡要地回顧一下本系列 上一篇文章中所提到的概念。其中的重要定義有:
解組:把 XML 數據轉化成 Java 類(或者多個類)的過程。
編組:把 Java 數據轉化成 XML 文檔的過程(恰恰與解組相反)。
語義等價:基於 XML 規則的相等。即使兩個文檔 看起來不同,但在語義上可能是等價的,參見上一篇文章中的例子。
往返:從 XML 文檔到 Java 代碼然後再回到 XML 的整個過程。有效的往返保證輸入和輸出文檔是相同的(語義等價)。
本文中將不那麼嚴格地使用這些術語,一定要真正掌握每個概念的含義。
還應該明白,本文以及後面的幾篇文章中,重點不一定是討論基本的功能,而使這種功能的實現。煤中數據綁定工具包都能編組和解組數據。但是許多工具包不那麼嚴格地執行這項任務,結果危害了往返的語義等價性。實現中的瑕疵(或者實現的功能不完整)是本系列中開始幾篇文章的重點,因此我要用幾篇文章來說明工具包的基本用法就不奇怪了,如果不知道它是否 真正有效, 使用一個工具包又有什麼意義呢?
最後,我假設您已經安裝並運行了 JAXB。您可以在 developerWorks上找到詳細描述安裝過程的大量文章,而且有了新的 Sun Java Web Services Developer Toolkit,安裝非常簡單。安裝好工具包並設置正確的類路徑,就萬事俱備了。
生成類
使用 JAXB 進行之前,首先要生成表示 XML 數據的 Java 類。這些例子中將使用一個非常簡單的 XML 文檔,如清單 1 所示。這是一份吉他的簡單列表,吉他是我的愛好之一。
清單 1. 簡單的 XML 文檔:吉他列表
<guitars>
<guitar id="10021">
<builder luthIEr="true">Ryan</builder>
<model>Mission Grand Concert</model>
<back-sides>Brazilian Rosewood</back-sides>
<top>Adirondack Spruce</top>
<notes>
<![CDATA[
Just unbelIEvable... this guitar has all the tone &
resonance you could ever want. I mean, <<WOW!!!>> This
is a lifetime guitar.
]]>
</notes>
</guitar>
<guitar id="0923">
<builder smallShop="true">Bourgeois</builder>
<model>OMC</model>
<back-sides>Bubinga</back-sides>
<top>Adirondack Spruce</top>
</guitar>
<guitar id="11091">
<builder>Martin & Company</builder>
<model>OM-28VR</model>
<back-sides>Indian Rosewood</back-sides>
<top bearclaw="true">Sitka Spruce</top>
<notes>It's certainly true that Martin isn't the only game in town anymore.
Still, the OM-28VR is one of their best models... and this one
has some fabulous bearclaw to boot. Nice specimen of a
still-important guitar manufacturer.
</notes>
</guitar>
</guitars>
使用 JAXB 時還需要一個 XML Schema 以生成類和數據結構。 清單 1的 XML Schema 如清單 2 所示。
清單 2. 清單 1 的 XML Schema
<?XML version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualifIEd">
<xs:element name="back-sides" type="xs:string"/>
<xs:element name="builder">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="luthIEr" default="false">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="smallShop" default="false">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
<xs:element name="guitar">
<xs:complexType>
<xs:sequence>
<xs:element ref="builder"/>
<xs:element ref="model"/>
<xs:element ref="back-sides"/>
<xs:element ref="top"/>
<xs:element ref="notes" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="id" type="xs:string" use="required"/>
</xs:complexType>
</xs:element>
<xs:element name="guitars">
<xs:complexType>
<xs:sequence>
<xs:element ref="guitar" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="model" type="xs:string"/>
<xs:element name="notes" type="xs:string"/>
<xs:element name="top">
<xs:complexType>
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="bearclaw" default="false">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:extension>
</xs:simpleContent>
</xs:complexType>
</xs:element>
</xs:schema>
基本步驟
准備好了 XML 和 XML Schema,生成 JAXB 類就很簡單了。確認已設置好命令行和環境,然後輸入以下命令:
xjc -p com.ibm.dw guitars.xsd -d src
一定要在和 guitars.xsd文件相同的目錄中執行上述命令,並且在工作目錄中建立一個 src目錄。如果沒有按這些步驟操作,就會出現某種 Java.io.IOException 錯誤。否則應該能看到一長串的輸出結果,如清單 3 所示。
清單 3. JAXB 類生成的輸出結果
C:\developerworks>xjc -p com.ibm.dw guitars.xsd -d src
parsing a schema...
compiling a schema...
com\ibm\dw\impl\runtime\MSVValidator.Java
com\ibm\dw\impl\runtime\SAXUnmarshallerHandlerImpl.Java
com\ibm\dw\impl\runtime\ErrorHandlerAdaptor.Java
com\ibm\dw\impl\runtime\AbstractUnmarshallingEventHandlerImpl.Java
com\ibm\dw\impl\runtime\UnmarshallableObject.Java
com\ibm\dw\impl\runtime\SAXMarshaller.Java
com\ibm\dw\impl\runtime\XMLSerializer.Java
com\ibm\dw\impl\runtime\ContentHandlerAdaptor.Java
com\ibm\dw\impl\runtime\UnmarshallingEventHandlerAdaptor.Java
com\ibm\dw\impl\runtime\SAXUnmarshallerHandler.Java
com\ibm\dw\impl\runtime\ValidatorImpl.Java
com\ibm\dw\impl\runtime\ValidatableObject.Java
com\ibm\dw\impl\runtime\UnmarshallerImpl.Java
com\ibm\dw\impl\runtime\NamespaceContext2.Java
com\ibm\dw\impl\runtime\Discarder.Java
com\ibm\dw\impl\runtime\NamespaceContextImpl.Java
com\ibm\dw\impl\runtime\ValidatingUnmarshaller.Java
com\ibm\dw\impl\runtime\UnmarshallingContext.Java
com\ibm\dw\impl\runtime\GrammarInfoImpl.Java
com\ibm\dw\impl\runtime\ValidationContext.Java
實在是有點太多了--注意,即使對於一個相當簡單的 XML Schema,JAXB 也創建了 大量的類。
對往返的影響
現在我已經介紹了基本的步驟,下面將實際分析一下其中到底發生了什麼。不必浪費時間回顧 JAXB 的基礎(其他文章已經做了很好的介紹),對於每個元素都有兩個源文件,一個文件和元素同名(比如,Guitar.java),另一個則在元素名後面加上 “Type”(如 GuitarType.Java)。這兩個文件都是接口,類的實現在子目錄 impl 下。這樣就生成了很多類--我認為有點太過分了。
但真正有意思的是這些類本身。要知道數據綁定實現的主要問題之一是往返--即從 XML 到 Java 代碼再返回到 XML 的過程中數據不會發生不可預料的變化的能力。換句話說,進去的是什麼出來的就是什麼。在目前,您還沒有准備好通過解組-編組循環測試輸出的結果(盡管以後要這樣做),首先來分析源代碼中可能存在的潛在問題。
第一個問題出現在任何數據綁定軟件包通常都會出問題的地方:類型化。即使有 XML Schema 的幫助,XML 也不一定能和 Java 類型很好的匹配。這通常意味著要損失一些數據類型信息,有可能摻入非法的數據。有時候問題出在 XML Schema 中,有時候則是因為 XML 到 Java 映射的局限性,必須仔細觀察。源代碼中發現的一個此類問題是 top 元素的表示。注意清單 4 中粗體顯示的那一行,這是 TopType 類的源代碼。
清單 4. TopType.Java 的源代碼
package com.ibm.dw;
public interface TopType {
Java.lang.String getValue();
void setValue(Java.lang.String value);
Java.lang.String getBearclaw();
void setBearclaw(Java.lang.String value);
}
回頭再看一看源文檔及其 XML Schema,很明顯 bearclaw 屬性的值應該是“true”或“false”。不幸的是,JAXB 沒有發現這一點並使用布爾數據類型, TopType 類的這個屬性可以接受任何字符串值。結果可能造成錯誤的數據。最終可能出現“True”、“true”、“tRUe”或者任何其他變化形式,而令使用 XML 的應用程序舉止失措。換句話說,您碰到了必須解決的一個問題域。
這類問題可能有以下不同的解決辦法:
手工編輯 TopType 類的源代碼,只接受布爾值。
向 TopType 方法中手工添加異常處理代碼,保證只能提供可以轉化成布爾值的字符串。
在 XML Schema 中創建表達布爾數據類型的新類型。
前兩種選擇非常明顯。第三種選擇也很簡單,盡管 W3C 那幫人實際上應該把這一條放在規范中說明。清單 5 給出了一個簡單的布爾類型定義:
清單 5. 模式中的布爾類型
<xsd:simpleType name="xsd:boolean">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="true"/>
<xsd:enumeration value="True"/>
<xsd:enumeration value="TRUE"/>
<xsd:enumeration value="false"/>
<xsd:enumeration value="False"/>
<xsd:enumeration value="FALSE"/>
</xsd:restriction>
</xsd:simpleType>
看起來不錯,是吧?但問題是它還不能解決這個問題。JAXB 根據 XML Schema 中 xsd:string 構造的用法,仍然會生成接受字符串參數的類。
在您准備告訴我這不成為一個問題之前,先讓我說明 JAXB 通過 什麼來保護您的數據。當從 Java 類編組回到 XML 時,將調用根據 XML Schema(和限制性的類型,如 清單 2和 清單 5所示)生成的驗證方法。換句話說,如果您為 bearclaw 屬性提供了一個值“foobar”,它就會被找出來。不過像“TRUe”、“fAlSe”和“tRue”這樣的值--當然也不想要這種結果--在驗證過程中也會被找出來。現在就需要使用 清單 5中詳細定義的類型, 還要注意“true”和“false”這兩個詞因為大小寫帶來的變化。這種繁雜的工作看起來意義不大。正是這類問題使得往返非常復雜,真正實現要比說起來困難得多。這也 恰恰是在選擇和使用數據綁定軟件包時應該考慮到的那類問題。
更加需要關注的是,至少對我而言,這樣可能造成超出單次往返過程的問題。要知道錯誤檢查只有在編組時進行,這意味著只要還在內存中,錯誤數據就可以自由地存在於這些成員變量之中等待編組。另外, 任何具有有限值集的性質都存在這種問題,而不僅僅是布爾值。但其中最值得注意的問題是,有時候 XML 文檔被讀入、處理然後供其他應用程序使用,而不是被編組回到 XML。因此所有的應用程序都有可能在這些字段中插入錯誤的數據,而其他任何使用數據的應用程序都會得到那個錯誤數據。除非希望每次訪問信息時都編組類,否則這個問題就確實存在。順便說一句,這些問題表明數據綁定相對而言還不夠成熟,而不僅僅是 JAXB。
我該怎麼做?
那麼您能做什麼呢?首先要堅持閱讀這些文章。我將詳細分析 JAXB,後面還將探討 Castor,嘗試標志出那些需要注意的地方。不知道問題的關鍵在哪裡,就不能編寫防彈代碼和錯誤檢查代碼;這正是本文以及後面幾篇文章的核心。更重要的是,要認識到即使最好的數據綁定軟件包,也需要一兩個很棒的程序員增加另外的保護措施,才能使其正確地運行。
最後還要記住,數據綁定並不總是魔法子彈。我並不想打消你們對數據綁定的興趣,恰恰相反,我認為它是一種了不起的應用程序。但是有時候一個簡單的 SAX 程序或者 DOM 樹就能提供需要的全部功能,就不需要再引入數據綁定項目的復雜性了。在以後的專欄中,我將分析使用數據綁定的最佳時機,什麼時候使用 SAX 和 DOM 更有效,並通過大量的例子幫助您作出決策。
結束語
顯然這裡關於 JAXB 的分析還不夠完全,但是您已經看到了研究數據綁定軟件包時有價值的分析方法。選擇一個數據綁定軟件包要比選擇喜歡的網站和單擊鏈接復雜得多,要保證選擇的應用程序能夠正確處理像往返這樣的問題。
JAXB 仍然不夠成熟,仍然是一種非常新的技術的較早主要版本。還要記住 JAXB 以前的幾個版本基本上已經廢棄了(還記得當時 JAXB 只能使用 DTD 嗎?它現在只能使用 XSD),因此 1.x 版是對這類問題真正的 第一次嘗試。並不說不應使用 JAXB,只是說必須小心謹慎。
下一篇文章,我將從類生成轉移到解組和編組,並說明它是如何工作的。我還將鑽研像空格、CDATA 節以及許多其他問題的處理。請繼續堅持,您將看到更多的代碼、更多的細節和更多的樂趣。下一次網上見!