從 XML 到 Java 程式碼的資料繫結(2):從 XML 資料建立類(轉)

amyz發表於2007-08-12
從 XML 到 Java 程式碼的資料繫結(2):從 XML 資料建立類(轉)[@more@]

  資料繫結系列的第二篇是如何從 XML 資料限制中生成一個 Java 語言。 本文透過完整的程式碼展現瞭如何生成類和程式碼,並提供瞭如何定製您自己版本的建議。 還沒有看過第一篇嗎?第一篇, "物件,無處不在的物件", 解釋了資料繫結是如何將 XML 和 Java 語言物件互為轉換。它比較了資料繫結和其它在 Java 程式中處理 XML 的方法, 並介紹了一個 XML 配置文件示例。第一部分也介紹了使用 XML Schema 來約束資料。

  在深入 Java 程式和 XML 程式碼之前,先快速回顧一下本系列第一部分所打下的基礎。

  在第一部分中,我們知道只要可以標識文件的一組約束,就可以將文件轉換成 Java 物件。那些約束為資料提供了介面。如 Web 服務配置文件示例中所示,XML 文件應當成為現有 Java 類的一個例項,並且從資料約束生成那個類。最後,會看到表示樣本 XML 文件約束的 XML schema。

  如果對細節還有疑問,請回顧 第一篇文章.

  打造基礎

  現在,可以著手從 XML schema 建立 Java 類。該類必須準確表示資料約束,並提供 Java 應用程式將使用的簡單讀方法和寫方法。開始之前,讓我們先回顧清單 1,檢視為 WebServiceConfiguration 文件定義的 XML schema。

  清單 1. 表示 Web 容器配置文件資料介面的 XML schema

  

  生成程式碼

  開始生成 Java 程式碼之前,首先必須確定核心類的名稱。將使用 org.enhydra.xml.binding 包中的 SchemaMapper,它是 Enhydra 應用伺服器實用程式類集合的一部分。還可以將任何必需的支援類放到這個包中。

  除了類名稱以外,還必須確定用來讀取和建立 XML 的 Java API。如上一篇文章中所討論過的,三種主要選擇是 SAX、DOM 和 JDOM。由於 SAX 僅僅適用於讀取 XML 文件,因此它不適合建立 XML。由於在打包階段中要將 Java 物件轉換為 XML 表示,因此在此階段中需要建立 XML。這就將選擇的範圍縮小到 DOM 和 JDOM。在這兩種選擇都可用的情況下,本例中我選擇使用 JDOM API,僅為了顯示其功能性(並不僅僅因為我是它的合著者之一!)。

  最後,必須指出如何將 XML schema 提供給 SchemaMapper 類。通常,可以假設類的生成是離線完成的(透過靜態 main 方法)。僅透過使 main 方法呼叫非靜態方法,還可以從執行時環境中使用類。做了這些決定後,就可以開始勾畫類的框架了。

  組裝 SchemaMapper 類框架

  要做的第一件事就是為要生成的程式碼設定一些基本儲存器。必須能夠從每個執行對映的 XML schema 生成多個介面和實現。Java HashMap 正好滿足要求。鍵是介面或實現名稱以及對映表中的值,該值是將要輸出到新 Java 程式檔案的實際程式碼。還需要儲存每對介面/實現的屬性(屬性是在這兩種類之間共享的)。這裡,我再次使用 HashMap。其中,鍵是介面名稱。但是,由於每個介面可能有多個屬性,因此該值是另一個具有屬性及其型別的 HashMap。最後,必須儲存 XML schema 的名稱空間,因為 JDOM 將使用這個名稱空間來訪問 XML schema 中的結構。所有這些具體資訊都足以初步勾畫出新類的框架,新類在清單 2 中。

  還請注意在清單 2 中已新增了兩個需要使用的基本方法:其中一個方法需要使用 XML schema 的 URL 來執行生成(允許它在網路可訪問 schema 以及本地 schema 下執行),另一個方法將類輸出到指定的目錄中。最後,簡單的 main 方法將 XML schema 看作一個變數,然後執行生成。

  清單 2. SchemaMapper 類的框架

  package org.enhydra.xml.binding;import java.io.File;import java.io.FileNotFoundException;import java.io.FileWriter;import java.io.IOException;import java.net.URL;import java.util.HashMap;import java.util.Map;import java.util.Iterator;import java.util.List;    // JDOM classes used for document representationimport org.jdom.Document;import org.jdom.Element;import org.jdom.JDOMException;import org.jdom.Namespace;import org.jdom.NoSuchAttributeException;import org.jdom.NoSuchChildException;import org.jdom.input.SAXBuilder;    /**

SchemaMapper handles generation of Java interfaces and classesfrom an XML schema, essentially allowing data contracts to be set upfor the binding of XML instance documents to Java objects.

@author Brett McLaughlin/public class SchemaMapper {    /** Storage for code for interfaces */private Map interfaces;/** Storage for code for implementations */private Map implementations;/** Properties that accessor/mutators should be created for */protected Map properties;/** XML Schema Namespace */private Namespace schemaNamespace;/** XML Schema Namespace URI */private static final String SCHEMA_NAMESPACE_URI ="";    /***

*Allocate storage and set up defaults.*

*/public SchemaMapper() {interfaces = new HashMap();implementations = new HashMap();properties = new HashMap();schemaNamespace = Namespace.getNamespace(SCHEMA_NAMESPACE_URI);}    /***

*This is the "entry point" for generation of Java classes from an XML*Schema. It allows a schema to be supplied, via URL,*and that schema is used for input to generation.*

** @param schemaURL URL at which XML Schema is located.* @throws IOException - when problems in generation occur.*/public void generateClasses(URL schemaURL) throws IOException {    // Perform generation}    /***

*This will write out the generated classes to the supplied stream.*

** @param directory File to write to (should be a directory).* @throws IOException - when output errors occur.*/public void writeClasses(File dir) throws IOException {    // Perform output to files}    /***

*This provides a static entry point for class generation from*XML Schemas.*

** @param args String[] list of files to parse.*/public static void main(String[] args) {SchemaMapper mapper = new SchemaMapper();try {for (int i=0; i

  In 清單 2中,可以看到對於每個作為自變數傳遞的 XML schema,main 方法都呼叫生成過程。首先,方法會生成類。將檔名轉換為 URL,並傳遞到 generateClasses(URL schemaURL) 。然後,透過 writeClasses(File dir) 方法將類寫到當前目錄中(轉換成 Java File: new File("."))。

  任何其它 Java 類都可以在執行時進行相同的呼叫,並生成類。例如,一個定製類裝入器也許能發現需要打包,確定仍要生成的介面和實現,並使用 SchemaMapper 類來執行該任務。所有這一切都在執行時完成。因為 generateClasses() 方法需要一個 URL,所以在網路上使用這個類非常簡單。例如,可以使用它來請求從 HTTP 上公開可用的 XML schema 生成類。

  由於對如何使用類做了儘量少的假設,因此它是一個普通類;程式可以同時在本地和遠端使用它。並且這個類可以當作一組 Java 語言和 XML 實用程式類的一部分,而不是必須以某種特殊形式使用的專用類。這種可重用性原則對 XML 特別關鍵,因為在不同系統上進行網路訪問和通訊是 XML 的基本前提。

  生成類

  構建好類的框架後,就可以新增類的主體了。

  我已經提到過生成過程具有遞迴性質。請記住這一點,需要填充 generateClasses() 方法才能開始。可以使用 JDOM 讀取 XML schema,然後從 schema 中抽取每個 complexType 元素。對於這些元素中的每一個,如清單 3 所示,遞迴程式從 handleComplexType() 呼叫處開始(以後將進一步討論)。

  清單 3. The generateClasses() 方法

  public void generateClasses(URL schemaURL) throws IOException {      /*** Create builder to generate JDOM representation of XML Schema,* without validation and using Apache Xerces.*/SAXBuilder builder = new SAXBuilder();try {Document schemaDoc = builder.build(schemaURL);     // Handle complex typesList complexTypes = schemaDoc.getRootElement().getChildren("complexType",schemaNamespace);for (Iterator i = complexTypes.iterator(); i.hasNext(); ) {     // Iterate and handleElement complexType = (Element)i.next();handleComplexType(complexType);}} catch (JDOMException e) {throw new IOException(e.getMessage());}}    

  為簡便起見,將強調一些重點,而不是詳細闡述將 schema 轉換為 Java 類的整個過程。可以 聯機檢視完整的 SchemaMapper 類,或者可以 下載它。

  生成器必須確定在 XML schema 中找到的每個 complexType 元素是 顯式的(具有“型別”屬性),還是 隱式的(沒有“型別”屬性)。如果型別是顯式的,則型別將成為介面名稱,並且首字母大寫。如果型別是隱式的,那麼將根據特性名稱構造介面名稱。清單 4 中顯示了處理這個邏輯的程式碼段。(如要了解更多資料繫結的定義,請參閱側欄, 術語解釋。)

  清單 4. 確定介面名稱

              // Determine if this is an explict or implicit typeString type = null;    // Handle extension, if neededString baseType = null;try {    // Assume that we are dealing with an explicit typetype = complexType.getAttribute("name").getValue();} catch (NoSuchAttributeException e) {     /** It is safe with an implicit type to assume that the parent* is of type "element", has no "type" attribute, and that we* can derive the type as the value of the element's "name"* attribute with the word "Type" appended to it.*/try {type = new StringBuffer().append(BindingUtils.initialCaps(complexType.getParent().getAttribute("name").getValue())).append("Type").toString();} catch (NoSuchAttributeException nsae) {    // Shouldn't happen in schema-valid documentsthrow new IOException("All elements must at have a name.");}}   

  因此,根據程式碼中的命名約定, 具有ServiceConfiguration 型別的元素將生成名為 ServiceConfiguration 的 Java 介面。名為 port 但 沒有顯式型別的元素將生成叫做 PortType 的 Java 介面。它採用元素名稱 ( port ),將首字母轉成大寫 ( Port ),再加上單詞 Type ,就得到了 PortType 。

  同樣,所有實現類都使用介面名稱,然後新增縮寫 Impl 。所以,最終實現類是 ServiceConfigurationImpl 和 PortTypeImpl 。

  使用這些命名約定,您可以很容易地確定將資料約束對映到 Java 介面會得到哪些 Java 類。如果設定了應用程式在執行時裝入類,那麼類裝入器或其它實用程式可以迅速確定是否已裝入了所需的類。類裝入器或實用程式只要從 XML schema 中找出生成的類名稱,然後嘗試裝入它們就可以了。命名邏輯是事先確定的,因此檢查起來非常方便。

  一旦確定了名稱,就可以生成介面和實現類的框架(請參閱清單 5)。

  清單 5. 生成程式碼

  StringBuffer interfaceCode = new StringBuffer();StringBuffer implementationCode = new StringBuffer();    /** Start writing out the interface and implementation class* definitions.*/interfaceCode.append("public interface ").append(interfaceName);    // Add in extension if appropriateif (baseType != null) {interfaceCode.append(" extends ").append(baseType);}interfaceCode.append(" {

");implementationCode.append("public class ").append(implementationName);    // Add in extension if appropriateif (baseType != null) {implementationCode.append(" extends ").append(baseType).append("Impl");}implementationCode.append(" implements ").append(interfaceName).append(" {

");                   // Add in properties and methods                        // Close up interface and implementation classesinterfaceCode.append("}");implementationCode.append("}");   

  實際上,生成屬性和方法是相當簡單的。將介面和相應實現的名稱新增到類的儲存器中,然後是右花括號,它們的作用是結束類。像這樣成對生成類,而不是單獨生成類,將使同時在介面和實現反映出該過程變得簡單。檢查原始碼(請參閱 參考資料),就可以得到足夠的解釋。

  清單 5中的粗體註釋表示源列表中的多行程式碼。在這裡精簡程式碼是為了保持簡潔。對於正在建立的 XML schema 的每個特性(由 schema attribute 表示),都會將讀方法和寫方法新增到介面和實現(實現還有執行方法邏輯的程式碼)。同時,將為實現類的程式碼新增變數。

  最終結果就是本系列第一部分中生成的類。可以在這裡檢視它們,或者與本文中的其餘程式碼一起下載(請參閱 參考資料):

  • ServiceConfiguration.java
  • ServiceConfigurationImpl.java
  • PortType.java
  • PortTypeImpl.java
  • DocumentType.java
  • DocumentTypeImpl.java
  • WebServiceConfiguration.java
  • WebServiceConfigurationImpl.java

  有兩個輔助程式類也將參與類生成:

  • BindingUtils ,將首字母變成大寫。雖然,可以將這個方法新增到生成器類,但我打算以後在打包和解包類時再使用該方法,所以我將它歸到這個輔助程式類中。可以 聯機檢視 BindingUtils ,或者可以 下載它。
  • DataMapping , SchemaMapper 類用來轉換資料型別。可以 聯機檢視原始碼或者 下載原始碼。

  完成包

  如許多其它開放原始碼軟體,在這裡顯示的資料繫結包是一項正在進行中的工作。雖然它已經初具規模,但仍有很大空間可用於新增更多功能和做改進。因此,以這段程式碼為基礎,可以有許多方式應用程式中加以衍生。

  可以重新使用該樣本程式碼,以將 XML schema 的資料約束轉換為型別安全的 Java 介面和實現。例如,迄今為止,示例程式碼還沒有處理 XML schema 中可能指定的範圍。而對於許多 XML 開發人員,那些資料範圍才是使用 schema 的真正原因。然後,請考慮清單 6 中 Web 服務的擴充 XML schema。

  清單 6. 帶擴充約束的 Web 服務配置

                                    

  清單 6說明了number屬性的型別, 並且在用紅色強調的幾行中指定了值的合法範圍(1 到 32,767)。當前版本的 SchemaMapper 將忽略這些附加宣告。從 schema 建立 Java 介面和實現類時,沒有必要處理 XML schema 中的 minXXX 和 maxXXX 關鍵字,但它們可以增加相當多的附加驗證。

  請檢視清單 7 中的程式碼示例,這些程式碼是可在實現類中生成的程式碼,以確保只有 schema 指定範圍中的值可以作為變數。

  清單 7. 帶範圍檢查的生成程式碼

  public class PortTypeImpl implements PortType {private String protocol;private int number;private String protected;                   public void setNumber(int number) {if ((number > 0) && (number <= 32767)) {this.number = number;} else {throw IllegalArgumentException("Argument must be greater than 0and less than or equal to 32767");}}            public int getNumber() {return number;}public void setProtocol(String protocol) {this.protocol = protocol;}public String getProtocol() {return protocol;}public void setProtected(String protected) {this.protected = protected;}public String getProtected() {return protected;}}   

  如果對類提供了非法值,那麼清單 7 中的生成程式碼塊將丟擲一個執行時異常,這樣既確保了型別安全性又確保了範圍安全性。

  可以很方便地將類似於清單 6 和清單 7 中的增強部分新增到我提供的基本程式碼中,因為本文中的所有程式碼完全都是開放原始碼。您也許還想加入 Enhydra 體系結構工作組郵件傳送清單,在該清單中維護和討論了該程式碼的未來版本和修訂本。可以從 Enhydra Web 站點上加入該清單,列在本文的 參考資料中。

  總結

  目前為止,應該已經瞭解什麼是資料繫結。已知道使用資料繫結的原因,特別是配置資訊。已經掌握如何建立 XML schema 和配置 Web 容器服務的 XML 例項文件,而且我們已經詳細討論了 org.enhydra.xml.binding.SchemaMapper 類。使用這個類,您可以建立 Java 介面和(該介面的)實現,它將管理從 XML 文件建立的 Java 例項。還知道如何將約束從 XML schema 對映到 Java。

  現在,已經可以進入下一部分。在下一部分中,將開始把 XML 文件實際轉換為 Java 物件的過程,其中 Java 物件是生成類的例項。下一篇文章將說明如何完成這個過程,及其逆向過程,以及 org.enhydra.xml.binding.Unmarshaller 和 org.enhydra.xml.binding.Marshaller 類。這兩個類將磁碟上文字的 XML 格式資料移到記憶體中的 Java 表示,然後再移回來。

  希望您能喜歡 XML schema 生成類,下次再見!

  以上所有原始碼均附在文件開始處的原始碼下載連結中。


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

相關文章