本數據綁定系列的第三部分演示了如何使用“JSR-031:數據綁定,Sun 數據綁定規范申請”中指定的方法,將 XML 元素和屬性轉換成 Java 對象。這部分主要講述從數據的 XML 表示移到應用程序代碼易於使用的 Java 實例。第三部分論及通過將 XML 文檔中的嵌套元素取消編組成 Java 對象、測試和用某些實際示例來使用新的工具。
本系列的目標是演示如何將 XML 元素轉換成 Java 對象,然後可以使用 Java 語言 accessor 和 mutator 方法直接處理 XML 數據。 第一部分比較了數據綁定和 Java 應用程序中其它處理 XML 數據的方法,分析了設計決策,還定義了示例 Web 服務配置文檔的 XML 模式。 第二部分說明了如何從 XML 模式生成接口和實現,以便符合 XML 模式的 XML 文檔可以轉換成這些生成類的實例。
在第三部分(共四部分)中,將完成基礎知識的講解,並且描述了如何精心設計代碼以執行取消編組,取消編組將完成將 XML 轉換成 Java 對象的過程。執行了取消編組後,可以使用測試類(已包括在內)來檢查是否所有部分都已正確組合在一起。本系列的每一部分都建立在其它部分的基礎之上,所以如果您還沒有看過第一和第二部分,您也許會看不懂本文中的一些描述。如果要回顧專門的詞匯表,請參閱 術語解釋側欄。
使用第一部分中為 WebServiceConfiguration 定義的 XML 模式(請參閱 更新版本 )和第二部分中的接口,即將創建為配置數據的特定實例提供數據的 XML 文檔。任何符合模式的 XML 文檔都可以編組成 Java 對象。這些對象應該是使用 SchemaMapper 類生成的類的實例。當然,最終結果就是數據綁定。
制作 XML 實例文檔
創建符合模式的 XML 文檔 -- 通常叫做 XML 實例-- 很簡單。文檔必須只提供與模式中定義的約束相匹配的數據值,如清單 1 所示。
清單 1. 符合示例 XML 模式的 XML 實例文檔
<?xml version="1.0"?>
<webServiceConfiguration xmlns="http://www.enhydra.org"
牋牋牋牋牋牋牋牋牋牋牋牋 xmlns:xsi="http://www.w3.org/1999/XMLSchema/instance"
牋牋牋牋牋牋牋牋牋牋牋牋 xsi:schemaLocation="http://www.enhydra.org
牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋牋 configuration.xsd"
牋牋牋牋牋牋牋牋牋牋牋牋 version="1.1"
牋牋牋牋牋牋牋牋牋牋牋牋 name="Unsecured Web Listener"
>
_<port number="80"
牋牋牋_protocol="http"
牋牋牋_protectedPort="false"
_/>
_<document root="/usr/local/enhydra/html"
牋牋牋牋牋_index="*.html,*.xml"
牋牋牋牋牋_error="error.html"
_/>
</webServiceConfiguration>
清單 1 中的示例完整地顯示了 WebServiceConfiguration 的實例。實例文檔包括了兩個名稱空間聲明。第一個是缺省名稱空間聲明,請參考 http://www.enhydra.org。這表示所有沒有前綴的元素會分配到此名稱空間。雖然,在本示例中不需要聲明缺省名稱空間,它還給予了文檔一些身份。這個缺省名稱空間有助於將該文檔與其它有相似或等同元素名稱的 XML 文檔區分出來。
定義的另一個名稱空間分配給 xsi 前綴,所以帶該前綴的所有元素都分配到此名稱空間。它 (http://www.w3.org/1999/XMLSchema/instance) 引用“XML 模式實例規范”的 URI。該規范依次定義了 XML 文檔如何引用文檔符合的 XML 模式。最後, schemaLocation 屬性引用 XML 模式。該屬性的第一個變量是受到約束的名稱空間(示例缺省名稱空間,它包括文檔中的每個元素)。第二個變量,用空格與第一個變量分開,引用 XML 模式的實際位置。本例中,模式 configuration.xsd 是一個本地文件,它與文檔在同一個目錄中。也可以通過使用 URL 來引用網絡上任意位置的模式。
在缺省名稱空間中,附加屬性(因為它們沒有前綴)定義了版本 (1.1) 和名稱 (Unsecured Web Listener)。
接著,聲明了模式中的 Port 對象,並定義了它的數據:端口號為 80,協議是 http。正確取消編組成 Java 代碼後,該文檔就變成了 WebServiceConfigurationImpl類的實例。然後,Java 代碼可以使用本系列第二部分中設計的接口 WebServiceConfiguration,以使用基本 XML 文檔中的數據。(請注意,可能會在應用程序中執行驗證,如 模式驗證側欄中所概述的。)
模式驗證
較新的 XML 語法分析器,如 Apache Xerces 語法分析器的當前發行版,允許對 XML 實例文檔執行模式驗證。驗證允許在程序格式上確保 XML 文檔符合它引用的 XML 模式。請與語法分析器供應商聯系或參考文檔,以確定語法分析器是否支持模式驗證,其驗證范圍,以及如何打開驗證。
打開前門
正式開始之前,需要提供入口點以取消編組 XML 文檔,該文檔作為返回 Java 對象的方法的輸入。(由於您會憶起,本例中取消編組的結果只是 Java 對象。)然後,該對象可以轉換成適當的接口,其實,您已經生成了該接口(在本系列第二部分中)。
對於示例 SchemaMapper 類,允許傳入 URL 是最有意義的。由於可以使用網絡資源作為輸入,而不是只允許文件名,這就提供了更多選擇。知道了這一點後,下一步就從 URL 創建 JDOM 文檔對象 ( org.jdom.Document ),然後處理文檔。請查看清單 2 中執行該操作的代碼。
清單 2. 將字符串映射成 Java 指定的類型
/**
*
* This method is the public entry point for unmarshalling an object from
* an XML instance document. *
* * @param instanceURL URL for the instance document.
* @return Object - the created Java Object, or * null if problems occur in a way
that does not * generate an Exception. * @throws IOException when errors in binding occur.
*/ public static Object unmarshall(URL
instanceURL) throws IOException { // Read in the document SAXBuilder builder = new SAXBuilder();
try { Document doc =
builder.build(instanceURL); Element rootElement = doc.getRootElement();
Unmarshaller unmarshaller = new Unmarshaller(); return
unmarshaller.getJavaRepresentation(rootElement); } catch (JDOMException e)
{ throw new IOException (e.getMessage()); } }
清單 2 中的方法是靜態的,允許直接調用它而無需實例化類的實例。由於對 unmarshall 方法的多個調用之間沒有需要共享的數據,因此該方法可以是靜態的。一旦處理了 XML,就將文檔的根元素(以 JDOM 表示)就被傳到執行從 XML 到 Java 對象轉換的內部方法。
轉換數據
我不打算逐行解釋取消編組中使用的完整代碼。可以查看 類的完整源碼清單 ,它基本上是不需加以說明的。但是,在入口點示例中,有一些值得強調的事情。如果創建了適當類的新實例,將使用 XML 文檔提供的值調用 mutator 方法(全都名為 setXXX )。當然,這將使 XML 數據在實例的 Java 方法中隨處都可用。清單 3 顯示了處理這種查找方法以及隨後調用的代碼片段。
清單 3. unmarshaller 類的入口點
// For each attribute, get its name and call mutator
List attributes = rootElement.getAttributes();
Method[] methods = objectClass.getMethods();
for (Iterator i = attributes.iterator(); i.hasNext(); ) {
Attribute att = (Attribute)i.next();
// Only want attributes for this namespace
if ((!att.getNamespace().equals(ns)) &&
(!att.getNamespace().equals(Namespace.NO_NAMESPACE))) {
continue;
}
// Determine method to call
String methodName = new StringBuffer()
.append("set")
.append(BindingUtils.initialCaps(att.getName()))
.toString();
// Find the method to call, and its parameter type
for (int j=0; j
找到了根元素的屬性,並確定了每個屬性的適用方法。然後,就是處理實際的 java.lang.reflect.Method 對象。XML 屬性的值已確定,並作為調用的參數傳送到方法。但是,需要解決一個映射問題;XML 文檔中的所有數據都作為 String 抽取,但傳遞時必須是適當的 Java 類型。清單 4 將一個方法添加到 DataMapping 輔助類中,以滿足轉換的需要。
清單 4 將字符串映射成 Java 特定的類型
/**
*
* This will take the Stringvalue supplied and convert it
* to an Object of the type specified in paramType. *
* * @param value String value to convert. * @param paramType Class with type to convert to.
* @return Object - value in correct type.
*/ public static Object getParameter(String value, Class paramType)
{ Object ob = null; String type = paramType.getName(); if
(type.equals("java.lang.String ")) { ob = value; } else if ((type.equals("int")) ||
(type.equals("java.lang.Integer")))
{ ob = Integer.valueOf(value); } else if ((type.equals("long")) ||
(type.equals("java.lang.Long"))) { ob = Long.valueOf(value); }
else if ((type.equals("float")) || (type.equals"java.lang.Float")))
{ ob = Float.valueOf(value); } else if ((type.equals("double"))
|| (type.equals("java.lang.Double"))) { ob = Double.valueOf(value); }
else if ((type.equals("boolean")) ||
(type.equals("java.lang.Boolean"))) { ob = Boolean.valueOf(value); }
return ob; }
在清單 4 中,值作為 String 傳入,並且還傳入了要轉換的類和處理類型轉換的方法。當然,這裡包含的數據類型不多。可以添加更多類型(如 java.util.Date )來支持更復雜的數據映射。
一旦數據轉換成適當的類型,可以使用反射調用 accessor 方法,並可傳入已轉換的數據類型。這就使 XML 文檔中的所有屬性及其值可以在作為結果的 Java 實例中以方法變量和的值存儲。
遞歸對象樹
所剩下的將是生成嵌套對象(如 WebServiceConfiguration 對象中的 PortType 對象)。最後,將嵌套對象傳遞給 accessor 方法,然後將填充了成員變量值和對象引用的頂級對象返回給調用程序。這種方式生成了一棵對象樹,其中主對象是該樹的主干。每個嵌套對象都形成了本身必須填充的樹的分枝。這些分枝可以有它們自己的分枝,帶有本身必須填充的的嵌套對象。由此可知,這棵樹可能變得非常復雜。
在如果同一操作必須發生不知多少次的情況下,遞歸幾乎總是完成操作的最佳選擇。如果是 unmarshaller 類,則需要在將 XML 綁定到 Java 的完整過程上遞歸。一旦讀取了所有屬性並將它們分配給已創建的 Java 實例,就需要取出每個嵌套元素,然後再次執行取消編組。
通過迭代所提供根的子元素,來完成 XML 文檔的處理,如清單 5 所示。這些子元素中的每一個都將成為另一個對象,這就表示必須以該元素作為根元素重新開始取消編組過程。
清單 5. 用遞歸處理嵌套元素
// Now do complex objects
List elements = rootElement.getChildren();
for (Iterator i = elements.iterator(); i.hasNext(); ) {
Element element = (Element)i.next();
// Only want elements for this namespace
if ((!element.getNamespace().equals(ns)) &&
(!element.getNamespace().equals(Namespace.NO_NAMESPACE))) {
continue;
}
// Determine method to call
String methodName = new
StringBuffer()
.append("set")
.append(BindingUtils.initialCaps(element.getName()))
.toString();
// Find the method to call, and its parameter type
for (int j=0; j<methods.length; j++) {
if (methods[j].getName().equals(methodName)) {
// Since all mutators have one param, get the first one
Class[] paramTypes =
methods[j].getParameterTypes();
Class paramType = paramTypes[0];
// Convert the type we have to the correct type
Object param = getJavaRepresentation(element);
// Invoke the method
methods[j].invoke(obj, new Object[] { param });
}
}
}
注:您也許注意到我在清單 5 中的取消編組中做了一個假設,即成員變量總是由 XML 屬性表示,嵌套對象由 XML 元素表示。那麼,這些元素可能有自己的屬性和嵌套元素。我的假設是唯一設置的限制,並且是合理的。這意味著取消編組過程不必查看引用的 XML 模式並確定特性是由元素表示,還是由屬性表示。這也會使編組過程變得更簡單,將在本系列的下一篇文章中出現。如果所有這一切對您沒有難度,那麼只使用屬性作為變量,元素作為對象,不必考慮嵌套和遞歸。
清單 5 中的代碼看來很像上一段代碼,其主要區別是用紅色強調的幾行。這段代碼通過取消編組嵌套元素來獲取參數,而不是使用 DataMapping 輔助類將文本值轉換成 Java 數據類型。然後,返回的對象提供給適當的 mutator 方法(例如, setPort ),迭代繼續進行。一旦遞歸從底層到頂層解開,則創建的 Java 對象將返回到調用應用程序。很快嗎!數據綁定完成了。
通過使用運行的 unmarshaller 類,實際上,它最終使用了數據綁定工具。通過使用 XML 模式、XML 文檔和一些簡單的 Java 代碼,訪問 XML 就象訪問 JavaBean 一樣簡單。
生成類
首先,確保已經從 XML 模式生成了 Java 類,如清單 6 所示。
清單 6. 從示例模式生成 Java 類
/projects/dev/binding> export CLASSPATH=/projects/dev/jdom/lib/xerces.jar
/projects/dev/binding> export CLASSPATH=$CLASSPATH:/projects/dev/jdom/build/jdom.jar
/projects/dev/binding> export CLASSPATH=$CLASSPATH:/projects/dev/binding
/projects/dev/binding> java org.enhydra.xml.binding.SchemaMapper xml/configuration.xsd
/projects/dev/binding> javac -d . *.java
使用 unmarshaller
如果已經從 XML 模式生成了類,並經過編譯,則可以繼續。作為確保類是否工作的簡單測試,可以使用清單 7 中的類測試數據綁定的功能性(或 下載這個類)。
清單 7. 數據綁定測試類
import java.io.File;
import org.enhydra.xml.binding.unmarshaller;
public class TestMapper {
public static void main(String[] args) {
System.out.println("Starting unmarshalling...");
try {
File file = new File("xml/example.xml");
Object o = unmarshaller. unmarshall(file.toURL());
System.out.println("Object class: " + o.getClass().getName());
System.out.println("Casting to WebServiceConfiguration...");
WebServiceConfiguration config = ( WebServiceConfiguration)o;
System.out.println("Successful cast.");
System.out.println("Name: " + config.getName());
System.out.println("Version: " + config.getVersion());
System.out.println("Port Number: " + config.getPort().getNumber());
System.out.println("Port Protocol: " + config.getPort().getProtocol());
} catch (Exception e) {
e.printStackTrace();
}
}
}
編譯和運行該數據綁定測試類以查看結果,如清單 8 所示。
清單 8. 測試 unmarshaller
/projects/dev/binding> javac -d . TestMapper.java
/projects/dev/binding> java TestMapper
Starting unmarshalling...
Object class: WebServiceConfiguration Impl
Casting to WebServiceConfiguration ...
Successful cast.
Name: Unsecured Web Listener
Version: 1.1
Port Number: 80
Port Protocol: http
啟動 Web 服務
作為一個更實用的示例,讓我們回顧已經在幾篇文章中提到的 Web 服務示例。假設有一些可以編程啟動的 Java 類(叫做 WebService ),那麼可簡單地使用數據綁定來獲取該類的配置信息。現在,從一個 XML 文檔(或者甚至幾個)中讀取和啟動新的 Web 偵聽程序是非常容易的事 -- 這不需要任何 XML 特定 API 的知識,如清單 9 所示。將配置數據取消編組成 Java 對象,然後使用標准 Java accessor 方法(通常是 getXXX() 格式)來配置新的 Web 服務。
清單 9. XML 到 Web 偵聽程序
// Assume we have a List of URLs
for (Iterator i = urls.iterator(); i.hasNext(); ) {
WebServiceConfiguration config = unmarshaller.unmarshal((URL)i.next());
WebService newService = new WebService();
newService.setName(config.getName());
// Set up port information
newService.setPortNumber(config.getPort().getNumber());
newService.setProtocol(config.getPort().getProtocol());
// Set up document root
newService.setDocRoot(config.getDocument().getRoot());
newService.setErrorPage(config.getDocument().getError());
newService.start();
}
就那麼簡單,即使是初級開發者也能寫出使用這個簡單 XML 文檔及其數據的 Java 程序,而他甚至還不知道正在使用 XML!有關 XML 數據綁定代碼的更多用法,請關注 Enhydra 應用服務器即將推出的新版本,在未來的發行版中將包含這裡討論的數據綁定類(並將在下一篇文章中繼續討論)。完成了 unmarshaller 的代碼之後,就可以討論最終細節了。
跟上不斷發展的 API
就在一個月之前,我們看到 SchemaMapper 類,它從 XML 模式生成 Java 接口和實現。該代碼很大程度地使用了 JDOM API(主要是因為它很方便,是我編寫的!)。然而 30 天時間只夠進行一屆曲棍球季後賽,對於 API,如仍在開發中的 JDOM,卻幾乎是一生一世。自上一篇文章以來,有幾個更改已經在 JDOM API 中生效了,大多數反映了一些更新的方法名。有關更改及其原因的詳細信息,請訪問 JDOM 網站(請參閱 參考資料),可以在該網站上加入 JDOM-興趣郵件列表。但是,為了幫助您使用最新和最好的版本, SchemaMapper 類再次出現在因特網上,並且已更新成使用最新版本的 JDOM(直接來自 CVS)。還可以 下載源碼。強烈建議從 CVS 獲取最新的 JDOM,並使用更新版本的代碼。(在第四部分到來之前,可能仍有更多更改。)
JSR-031,數據綁定 API,在 Java 社區中仍是處在爭論和測試過程的建議書。在這個過程中,它還可能做一些更改。盡管它還未成熟,至今為止許多使用 XML 的 Java 開發者還是會使用它,因為它是執行非常有用功能的方法。
結束語
通過使用本系列這部分中新的詳細信息,可以使用數據綁定代碼。使用 unmarshaller 類,就可以在 Java 代碼中方便地使用 XML 文檔,而不必直接借助於 XML API,如 DOM、SAX 或 JDOM。雖然示例建議使用數據綁定處理配置文件,您也許已經有了在應用程序中使用數據綁定的其它想法。也可以使用數據綁定代碼來進行消息傳遞、數據存儲和顯示等等。
本系列的第四篇,也就是最後一篇文章將主要講述編組,即利用 Marshaller 類得到 Java 類,並將它轉換成 XML 文檔。該文章將討論轉換原來經過取消編組的 Java 對象,以及未經過取消編組的 Java 對象。到那時,希望您喜歡迄今為止出現的數據綁定代碼,下次再見。
術語解釋
數據綁定。一種使用 JSP-031 訪問 Java 中 XML 數據的新方法。JSP-031 是一個仍在開發中的 API。
顯式類型。具有類型屬性的 complexType 元素。模式中的顯式類型成為生成的 Java 代碼中的接口名稱。
隱式類型。 不具有類型屬性的 complexType 元素。這種情況下,接口名稱由 SchemaMapper 生成。
JSR-031。Sun 公司仍在開發中的一種新的 Java 規范申請。它用於將 XML 文檔編譯成一個或多個 Java 類,而在 Java 應用程序中可以方便地使用這些 Java 類。
編組。 將 Java 對象轉換為 XML 表示,擁有當前值。
取消編組。 根據 XML 對象創建 Java 對象,通常是根據編組生成一個 Java 對象。
本文所有源代碼已經包含在文檔開始處的源代碼包中。
本文配套源碼