從 XML 到 Java 程式碼的資料繫結(3):從文字到位元組碼(轉)

amyz發表於2007-08-12
從 XML 到 Java 程式碼的資料繫結(3):從文字到位元組碼(轉)[@more@]

  本資料繫結系列的第三部分演示瞭如何使用“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 例項文件

  __

  清單 1 中的示例完整地顯示了 WebServiceConfiguration 的例項。例項文件包括了兩個名稱空間宣告。第一個是預設名稱空間宣告,請參考 。這表示所有沒有字首的元素會分配到此名稱空間。雖然,在本示例中不需要宣告預設名稱空間,它還給予了文件一些身份。這個預設名稱空間有助於將該文件與其它有相似或等同元素名稱的 XML 文件區分出來。

  定義的另一個名稱空間分配給 xsi 字首,所以帶該字首的所有元素都分配到此名稱空間。它 () 引用“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 waythat does not * generate an Exception. * @throws IOException when errors in binding occur.*/ public static Object unmarshall(URLinstanceURL) throws IOException { // Read in the document SAXBuilder builder = new SAXBuilder();try { Document doc =builder.build(instanceURL); Element rootElement = doc.getRootElement();Unmarshaller unmarshaller = new Unmarshaller(); returnunmarshaller.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

  注:您也許注意到我在清單 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 TestMapperStarting unmarshalling...Object class: WebServiceConfiguration ImplCasting to WebServiceConfiguration ...Successful cast.Name: Unsecured Web ListenerVersion: 1.1Port Number: 80Port 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 物件。

  本文所有原始碼已經包含在文件開始處的原始碼包中。


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

相關文章