數據綁定系列的第二篇是如何從 xml 數據限制中生成一個 java 語言。本文通過完整的代碼展現了如何生成類和代碼,并提供了如何定制您自己版本的建議。還沒有看過第一篇嗎?第一篇,"對象,無處不在的對象", 解釋了數據綁定是如何將 XML 和 Java 語言對象互為轉換。它比較了數據綁定和其它在 Java 程序中處理 XML 的方法, 并介紹了一個 XML 配置文檔示例。第一部分也介紹了使用 XML Schema 來約束數據。
在深入 Java 程序和 XML 代碼之前,先快速回顧一下本系列第一部分所打下的基礎。
在第一部分中,我們知道只要可以標識文檔的一組約束,就可以將文檔轉換成 Java 對象。那些約束為數據提供了接口。如 Web 服務配置文檔示例中所示,XML 文檔應當成為現有 Java 類的一個實例,并且從數據約束生成那個類。最后,會看到表示樣本 XML 文檔約束的 XML schema。
假如對細節(jié)還有疑問,請回顧第一篇文章.
打造基礎
現在,可以著手從 XML schema 創(chuàng)建 Java 類。該類必須準確表示數據約束,并提供 Java 應用程序將使用的簡單讀方法和寫方法。開始之前,讓我們先回顧清單 1,查看為 WebServiceConfiguration 文檔定義的 XML schema。
除了類名稱以外,還必須確定用來讀取和創(chuàng)建 XML 的 Java API。如上一篇文章中所討論過的,三種主要選擇是 SAX、DOM 和 JDOM。由于 SAX 僅僅適用于讀取 XML 文檔,因此它不適合創(chuàng)建 XML。由于在打包階段中要將 Java 對象轉換為 XML 表示,因此在此階段中需要創(chuàng)建 XML。這就將選擇的范圍縮小到 DOM 和 JDOM。在這兩種選擇都可用的情況下,本例中我選擇使用 JDOM API,僅為了顯示其功能性(并不僅僅因為我是它的合著者之一!)。
最后,必須指出如何將 XML schema 提供給 SchemaMapper 類。通常,可以假設類的生成是脫機完成的(通過靜態(tài) main 方法)。僅通過使 main 方法調用非靜態(tài)方法,還可以從運行時環(huán)境中使用類。做了這些決定后,就可以開始勾畫類的框架了。更多的請看:http://www.QQread.com/windows/2003/index.Html組裝 SchemaMapper 類框架
要做的第一件事就是為要生成的代碼設置一些基本存儲器。必須能夠從每個執(zhí)行映射的 XML schema 生成多個接口和實現。Java HashMap 正好滿足要求。鍵是接口或實現名稱以及映射表中的值,該值是將要輸出到新 Java 程序文件的實際代碼。還需要存儲每對接口/實現的屬性(屬性是在這兩種類之間共享的)。這里,我再次使用 HashMap。其中,鍵是接口名稱。但是,由于每個接口可能有多個屬性,因此該值是另一個具有屬性及其類型的 HashMap。最后,必須存儲 XML schema 的名稱空間,因為 JDOM 將使用這個名稱空間來訪問 XML schema 中的結構。所有這些具體信息都足以初步勾畫出新類的框架,新類在清單 2 中。
還請注重在清單 2 中已添加了兩個需要使用的基本方法:其中一個方法需要使用 XML schema 的 URL 來執(zhí)行生成(答應它在網絡可訪問 schema 以及本地 schema 下運行),另一個方法將類輸出到指定的目錄中。最后,簡單的 main 方法將 XML schema 看作一個變量,然后執(zhí)行生成。
* <code>SchemaMapper</code> handles generation of Java interfaces and classes * from an XML schema, essentially allowing data contracts to be set up * for the binding of XML instance documents to Java objects. * </p> * * @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 = "http://www.w3.org/1999/xmlSchema";
/** * <p> * Allocate storage and set up defaults. * </p> */ public SchemaMapper() { interfaces = new HashMap(); implementations = new HashMap(); properties = new HashMap(); schemaNamespace = Namespace.getNamespace(SCHEMA_NAMESPACE_URI); }
/** * <p> * This is the "entry point" for generation of Java classes from an XML * Schema. It allows a schema to be supplied, via <code>URL</code>, * and that schema is used for input to generation. * </p> * * @param schemaURL <code>URL</code> at which XML Schema is located. * @throws <code>IOException</code> - when problems in generation occur. */ public void generateClasses(URL schemaURL) throws IOException { // Perform generation }
/** * <p> * This will write out the generated classes to the supplied stream. * </p> * * @param Directory <code>File</code> to write to (should be a directory). * @throws <code>IOException</code> - when output errors occur. */ public void writeClasses(File dir) throws IOException { // Perform output to files }
/** * <p> * This provides a static entry point for class generation from * XML Schemas. * </p> * * @param args <code>String[]</code> list of files to parse. */ public static void main(String[] args) { SchemaMapper mapper = new SchemaMapper(); try { for (int i=0; i<args.length; i++) { File file = new File(args[i]); mapper.generateClasses(file.toURL()); mapper.writeClasses(new File(".")); } } catch (FileNotFoundException e) { System.out.println("Could not locate XML Schema: "); e.printStackTrace(); } catch (IOException e) { System.out.println("Java class generation failed: "); e.printStackTrace(); } } } In 清單 2 中,可以看到對于每個作為自變量傳遞的 XML schema,main 方法都調用生成過程。首先,方法會生成類。將文件名轉換為 URL,并傳遞到 generateClasses(URL schemaURL)。然后,通過 writeClasses(File dir) 方法將類寫到當前目錄中(轉換成 Java File: new File("."))。
由于對如何使用類做了盡量少的假設,因此它是一個普通類;程序可以同時在本地和遠程使用它。并且這個類可以當作一組 Java 語言和 XML 實用程序類的一部分,而不是必須以某種非凡形式使用的專用類。這種可重用性原則對 XML 非凡要害,因為在不同系統上進行網絡訪問和通信是 XML 的基本前提。 更多的請看:http://www.qqread.com/windows/2003/index.html生成類
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();
生成器必須確定在 XML schema 中找到的每個 complexType 元素是顯式的(具有“類型”屬性),還是隱式的(沒有“類型”屬性)。假如類型是顯式的,則類型將成為接口名稱,并且首字母大寫。假如類型是隱式的,那么將根據特性名稱構造接口名稱。清單 4 中顯示了處理這個邏輯的代碼段。(如要了解更多數據綁定的定義,請參閱側欄,術語解釋。)
清單 4. 確定接口名稱 // Determine if this is an eXPlict or implicit type
String type = null; // Handle extension, if needed String baseType = null;
try { // Assume that we are dealing with an explicit type type = 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 documents throw new IOException("All elements must at have a name."); } } 因此,根據代碼中的命名約定,具有 ServiceConfiguration 類型的元素將生成名為 ServiceConfiguration 的 Java 接口。名為 port 但沒有顯式類型的元素將生成叫做 PortType 的 Java 接口。它采用元素名稱 (port),將首字母轉成大寫 (Port),再加上單詞 Type,就得到了 PortType。
// Close up interface and implementation classes interfaceCode.append("}"); implementationCode.append("}"); 實際上,生成屬性和方法是相當簡單的。將接口和相應實現的名稱添加到類的存儲器中,然后是右花括號,它們的作用是結束類。像這樣成對生成類,而不是單獨生成類,將使同時在接口和實現反映出該過程變得簡單。檢查源代碼(請參閱參考資料),就可以得到足夠的解釋。
清單 5 中的粗體注釋表示源列表中的多行代碼。在這里精簡代碼是為了保持簡潔。對于正在創(chuàng)建的 XML schema 的每個特性(由 schema attribute 表示),都會將讀方法和寫方法添加到接口和實現(實現還有執(zhí)行方法邏輯的代碼)。同時,將為實現類的代碼添加變量。
可以重新使用該樣本代碼,以將 XML schema 的數據約束轉換為類型安全的 Java 接口和實現。例如,迄今為止,示例代碼還沒有處理 XML schema 中可能指定的范圍。而對于許多 XML 開發(fā)人員,那些數據范圍才是使用 schema 的真正原因。然后,請考慮清單 6 中 Web 服務的擴充 XML schema。
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 0 and 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; }
可以很方便地將類似于清單 6 和清單 7 中的增強部分添加到我提供的基本代碼中,因為本文中的所有代碼完全都是開放源碼。您也許還想加入 Enhydra 體系結構工作組郵件發(fā)送清單,在該清單中維護和討論了該代碼的未來版本和修訂本。可以從 Enhydra Web 站點上加入該清單,列在本文的參考資料中。
總結
目前為止,應該已經了解什么是數據綁定。已知道使用數據綁定的原因,非凡是配置信息。已經把握如何創(chuàng)建 XML schema 和配置 Web 容器服務的 XML 實例文檔,而且我們已經具體討論了 org.enhydra.xml.binding.SchemaMapper 類。使用這個類,您可以創(chuàng)建 Java 接口和(該接口的)實現,它將治理從 XML 文檔創(chuàng)建的 Java 實例。還知道如何將約束從 XML schema 映射到 Java。
現在,已經可以進入下一部分。在下一部分中,將開始把 XML 文檔實際轉換為 Java 對象的過程,其中 Java 對象是生成類的實例。下一篇文章將說明如何完成這個過程,及其逆向過程,以及 org.enhydra.xml.binding.Unmarshaller 和 org.enhydra.xml.binding.Marshaller 類。這兩個類將磁盤上文本的 XML 格式數據移到內存中的 Java 表示,然后再移回來。