從XML到Java的數據綁定之三
本文 轉自 IBM developerWorks 中國網站
本數據綁定系列的第三部分演示了如何使用“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 String value 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: WebServiceConfigurationImpl 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 對象。到那時,希望您喜歡迄今為止出現的數據綁定代碼,下次再見。