首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JiBX 1.2 進階教學:從 XML Schema 產生 Java 代碼(下)

     編輯:Java 相關知識

TimeCard 產生的代碼結構

hrxml 目錄下的 Ant build.xml 定義了多個目標,用於為 TimeCard Schema 產生基礎代碼,涵蓋預設產生與多種自訂範例。範例目錄還包含測試程式 org.jibx.hrxml.Test,它會使用產生的資料模型類別將範例文件解組,再重新編組並與原始文件比對。目錄中也備有 HR-XML 發行版的測試文件集。codegen 目標以預設值執行 CodeGen,compile 編譯產生的代碼與測試代碼,bind 編譯 JiBX 綁定,roundtrip 執行測試程式,而 full 則依序執行所有步驟。

大多數透過 Schema 產生代碼的方式,會為每個 complexType 定義及列舉 simpleType 產生獨立類別。CodeGen 透過檢查引用與內聯定義、忽略未使用的 include/import 定義,通常能減少產生的類別數量。TimeCard Schema 共有 10 個全域 complexType、23 個區域 complexType 及 8 個列舉 simpleType,預設產生的資料模型包含 15 個頂層類別與 23 個內部類別,比 Schema 組件總數少。稍後將示範如何透過自訂進一步簡化模型。

<xs:choice> 處理機制

程式碼清單 8 展示 CodeGen 如何處理 TimeCardType 中兩個元素的選擇結構。預設情況下,CodeGen 使用一個選擇變數追蹤目前作用中的選項。選項包含的值的 setter 允許寫入新值,但無法直接切換選項(嘗試會拋出 IllegalStateException)。若需在設定後變更選項,必須先呼叫清除方法(如 clearReportedResourceSelect())重置狀態。

程式碼清單 8. HR-XML TimeCard 產生的代碼範例

/** 
 * Schema fragment(s) for this class: 
 * <pre> 
 * <xs:complexType xmlns:ns="https://ns.hr-xml.org/2007-04-15" 
 * xmlns:ns1="https://www.w3.org/XML/1998/namespace" 
 * xmlns:xs="https://www.w3.org/2001/XMLSchema" name="TimeCardType"> 
 * <xs:sequence> 
 * <xs:element type="ns:EntityIdType" name="Id" minOccurs="0"/> 
 * <xs:element name="ReportedResource"> 
 * <xs:complexType> 
 * <xs:choice> 
 * <xs:element type="ns:TimeCardPersonType" name="Person"/> 
 * <xs:element name="Resource"> 
 * <!-- Reference to inner class Resource --> 
 * </xs:element> 
 * </xs:choice> 
 * </xs:complexType> 
 * </xs:element> 
 * ... */ 
public class TimeCardType { 
    private EntityIdType id; 
    private int reportedResourceSelect = -1; 
    private final int REPORTED_RESOURCE_PERSON_CHOICE = 0; 
    private final int RESOURCE_CHOICE = 1; 
    private TimeCardPersonType reportedResourcePerson; 
    private Resource resource; 
    ... 
    private void setReportedResourceSelect(int choice) { 
        if (reportedResourceSelect == -1) { 
            reportedResourceSelect = choice; 
        } else if (reportedResourceSelect != choice) { 
            throw new IllegalStateException( 
                "Need to call clearReportedResourceSelect() before changing existing choice"); 
        } 
    } 
    /** Clear the choice selection. */ 
    public void clearReportedResourceSelect() { 
        reportedResourceSelect = -1; 
    } 
    /** Check if ReportedResourcePerson is current selection for choice. */ 
    public boolean ifReportedResourcePerson() { 
        return reportedResourceSelect == REPORTED_RESOURCE_PERSON_CHOICE; 
    } 
    /** Get the 'Person' element value. */ 
    public TimeCardPersonType getReportedResourcePerson() { 
        return reportedResourcePerson; 
    } 
    /** Set the 'Person' element value. */ 
    public void setReportedResourcePerson(TimeCardPersonType reportedResourcePerson) { 
        setReportedResourceSelect(REPORTED_RESOURCE_PERSON_CHOICE); 
        this.reportedResourcePerson = reportedResourcePerson; 
    } 
    /** Check if Resource is current selection for choice. */ 
    public boolean ifResource() { 
        return reportedResourceSelect == RESOURCE_CHOICE; 
    } 
    /** Get the 'Resource' element value. */ 
    public Resource getResource() { 
        return resource; 
    } 
    /** Set the 'Resource' element value. */ 
    public void setResource(Resource resource) { 
        setReportedResourceSelect(RESOURCE_CHOICE); 
        this.resource = resource; 
    } 
}

這種選擇處理對大多數應用運作良好,能防止在單一選擇中設定多個備選值。若不偏好此機制,可透過自訂輕鬆修改。choice-check 屬性控制如何在產生代碼中檢查 <xsd:choice> 狀態:

  • disable:停用所有檢查,不追蹤狀態,允許每個選項設定一個值。
  • checkset(預設):僅 setter 檢查目前設定並拋出異常。
  • checkboth:getter 也檢查狀態,不符時拋出異常。
  • override:設定任何值時皆變更狀態,不拋出異常。

choice-exposed 配合追蹤狀態的設定使用:false(預設)將常數、變數、方法設為私有;true 為狀態變數加入 getter,方便用 switch 依狀態執行不同邏輯。這兩屬性可在任意自訂層級使用,便於統一設定行為並保留個別調整彈性。

<xs:any> 與 mixed="true" 處理

企業 Schema 常用 <xs:any> 建立擴充點。預設 CodeGen 以 org.w3c.dom.Element 物件(maxOccurs > 1 時用 List)處理,提供處理任意符合定義文件的彈性。程式碼清單 9 展示對應清單 7 Schema 片段的產生代碼,因 maxOccurs="unbounded" 而使用 Element 列表。

程式碼清單 9. <xs:any> 產生的代碼範例

/** 
 * ... 
 * Schema fragment(s) for this class: 
 * <pre> 
 * <xs:complexType xmlns:xs="https://www.w3.org/2001/XMLSchema" mixed="true" 
 * name="AdditionalDataType"> 
 * <xs:sequence> 
 * <xs:any minOccurs="0" maxOccurs="unbounded" processContents="strict" 
 * namespace="##any"/> 
 * </xs:sequence> 
 * <xs:attribute type="xs:string" name="type"/> 
 * </xs:complexType> 
 * </pre> */ 
public class AdditionalDataType { 
    private List<Element> anyList = new ArrayList<Element>(); 
    private String type; 
    /** Get the list of sequence items. */ 
    public List<Element> getAny() { 
        return anyList; 
    } 
    /** Set the list of sequence items. */ 
    public void setAny(List<Element> list) { 
        anyList = list; 
    } 
    ... 
}

清單 9 的 Schema 定義有幾點被忽略或部分處理:mixed="true" 允許字元資料與元素混合,但產生的模型無空間保存字元內容,解組時會捨棄;processContents="strict" 要求實例文件元素皆有 Schema 定義,CodeGen 忽略此屬性;namespace 限制也被忽略。可透過 any-handling 自訂屬性選擇其他處理方式:discard 忽略並捨棄對應元素;dom 為預設;mapped 要求每個匹配元素有全域 Schema 定義,以 java.lang.Object 表示,執行期型別對應全域定義。

<xs:simpleType> 處理限制

同樣地,CodeGen 忽略或部分處理 <xs:simpleType> 定義的多個面向。<xs:simpleType> 限制(長度、範圍、正則)中,僅 <xs:enumeration> 在產生模型中強制執行。<xs:simpleType> <union> 也被忽略。程式碼清單 10 顯示對應 <xs:union> 參考的產生代碼與原始 Schema 片段,所有指向 union 型別的參考(如 TimeCardDuration、AnyDateTimeType)在代碼中均以簡單 String 表示。

程式碼清單 10. <xs:union> 產生的代碼範例與原始 Schema

/** 
 * Schema fragment(s) for this class: 
 * <pre> 
 * <xs:element xmlns:ns="https://ns.hr-xml.org/2007-04-15" 
 * xmlns:xs="https://www.w3.org/2001/XMLSchema" name="TimeInterval"> 
 * <xs:complexType> 
 * <xs:sequence> 
 * <xs:element type="ns:EntityIdType" name="Id" minOccurs="0"/> 
 * <xs:element type="xs:string" name="StartDateTime"/> 
 * <xs:choice> 
 * <xs:sequence> 
 * <xs:element type="xs:string" name="EndDateTime"/> 
 * <xs:element type="xs:string" name="Duration" minOccurs="0"/> 
 * </xs:sequence> 
 * <xs:element type="xs:string" name="Duration"/> 
 * </xs:choice> 
 * ... 
 * </pre> */ 
public static class TimeInterval { 
    private EntityIdType id; 
    private String startDateTime; 
    private int choiceSelect = -1; 
    private final int END_DATE_TIME_CHOICE = 0; 
    private final int DURATION_CHOICE = 1; 
    private String endDateTime; 
    private String duration; 
    private String duration1; 
    ... 
    ... 
    <xsd:element name="TimeInterval"> 
    <xsd:complexType> 
    <xsd:sequence> 
    <xsd:element name="Id" type="EntityIdType" minOccurs="0"/> 
    <xsd:element name="StartDateTime" type="AnyDateTimeType"/> 
    <xsd:choice> 
    <xsd:sequence> 
    <xsd:element name="EndDateTime" type="AnyDateTimeType"/> 
    <xsd:element name="Duration" type="TimeCardDuration" minOccurs="0"/> 
    </xsd:sequence> 
    <xsd:element name="Duration" type="TimeCardDuration"/> 
    </xsd:choice> 
    ... 
    <xsd:simpleType name="TimeCardDuration"> 
    <xsd:union memberTypes="xsd:duration xsd:decimal"/> 
    </xsd:simpleType>

Schema 轉換

比較清單 10 頂部 Javadoc 內嵌的 Schema 片段與底部實際片段,會發現初始 Schema 中的 union simpleType 參考在 Javadoc 版本已被 xs:string 取代。這是刻意設計,代表 CodeGen 執行的多種 Schema 結構轉換之一。移除 <union> simpleType 及非列舉的 simpleType 限制等轉換均硬編碼在 CodeGen 中,其他轉換受自訂控制。無論哪種方式,Javadoc 包含的 Schema 片段總是顯示轉換後的版本,因為實際用於產生代碼的就是轉換後的 Schema。

自訂資料模型

教程前面的範例展示簡單的 CodeGen 自訂。了解預設處理 HR-XML TimeCard Schema 後,接著探討更強大的自訂。

改善預設模型的缺點

預設產生的模型代碼有幾個弱點:Schema 型別名稱皆以 Type 結尾,導致類別名稱過長;套件名 org.hrxml.ns 雖合理,但若能表明專用於 TimeCard 文件更佳。程式碼清單 11 顯示另一缺點:java.math.BigInteger 用於表示 xs:integer,雖為標準 Java 類別中最精確表示,但不如 int 或 Integer 好用。更糟的是,即使 xs:int 更適合,開發者常用 xs:integer 撰寫 Schema,導致產生代碼出現 BigInteger。本例中 GenderCode 允許值皆為個位數(見清單底部原始 Schema)。

程式碼清單 11. xs:integer 產生範例

/** 
 * Schema fragment(s) for this class: 
 * <pre> 
 * <xs:element xmlns:xs="https://www.w3.org/2001/XMLSchema" type="xs:integer" 
 * name="GenderCode"/> 
 * </pre> */ 
public class GenderCode { 
    private BigInteger genderCode; 
    /** Get the 'GenderCode' element value. */ 
    public BigInteger getGenderCode() { 
        return genderCode; 
    } 
    /** Set the 'GenderCode' element value. */ 
    public void setGenderCode(BigInteger genderCode) { 
        this.genderCode = genderCode; 
    } 
} 
<xsd:simpleType name="GenderCodeType"> 
<xsd:annotation> 
<xsd:documentation>Must conform to ISO 5218 - Representation of Human Sexes (0 - Not Known; 1 - Male; 2 - Female; 9 - Not specified)</xsd:documentation> 
</xsd:annotation> 
<xsd:restriction base="xsd:integer"> 
<xsd:pattern value="[0129]"/> 
</xsd:restriction> 
</xsd:simpleType>

程式碼清單 12. TimeCard 自訂範例

<schema-set xmlns:xs="https://www.w3.org/2001/XMLSchema" 
    package="org.hrxml.timecard" 
    type-substitutions="xs:integer xs:int"> 
    <name-converter strip-suffixes="Type"/> 
    <class-decorator class="org.jibx.schema.codegen.extend.CollectionMethodsDecorator"/> 
</schema-set>

清單 12 的自訂改善上述缺點:package="org.hrxml.timecard" 指定產生類別的 Java 套件;type-substitutions="xs:integer xs:int" 定義 Schema 型別置換,以 xs:int 取代 xs:integer;巢狀 name-converter 的 strip-suffixes="Type" 移除名稱尾端的 Type;class-decorator 加入 CollectionMethodsDecorator,為集合值新增實用支援方法。CodeGen 建構代碼時按順序呼叫所有配置的修飾器,修飾器可修改或新增欄位、方法、類別結構,使用 Eclipse AST 實作。提供的修飾器(含 SerializableDecorator)示範如何擴充 CodeGen,原始碼是撰寫自訂修飾器的最佳起點。

可用 custgen1 Ant 目標嘗試清單 12 自訂,或用 custom1 執行完整產生、編譯、綁定、測試。清單 13 顯示應用自訂後的結果:TimeCardType 改為 TimeCard,除 List getter/setter 外新增 size、add、索引 get、clear 方法;GenderCode 中 BigInteger 改為 int 原生型別。

程式碼清單 13. 自訂的資料模型

/** 
 * Schema fragment(s) for this class: 
 * <pre> 
 * ... 
 * </pre> */ 
public class TimeCard { 
    ... 
    private List<ReportedTime> reportedTimeList = new ArrayList<ReportedTime>(); 
    ... 
    /** Get the list of 'ReportedTime' element items. */ 
    public List<ReportedTime> getReportedTimes() { 
        return reportedTimeList; 
    } 
    /** Set the list of 'ReportedTime' element items. */ 
    public void setReportedTimes(List<ReportedTime> list) { 
        reportedTimeList = list; 
    } 
    /** Get the number of 'ReportedTime' element items. */ 
    public int sizeReportedTime() { 
        return reportedTimeList.size(); 
    } 
    /** Add a 'ReportedTime' element item. */ 
    public void addReportedTime(ReportedTime item) { 
        reportedTimeList.add(item); 
    } 
    /** Get 'ReportedTime' element item by position. */ 
    public ReportedTime getReportedTime(int index) { 
        return reportedTimeList.get(index); 
    } 
    /** Remove all 'ReportedTime' element items. */ 
    public void clearReportedTime() { 
        reportedTimeList.clear(); 
    } 
    ... 
} 
/** 
 * Schema fragment(s) for this class: 
 * <pre> 
 * &lt;xs:element xmlns:xs="https://www.w3.org/2001/XMLSchema" type="xs:int" 
 * name="GenderCode"/> 
 * </pre> */ 
public class GenderCode { 
    private int genderCode; 
    /** Get the 'GenderCode' element value. */ 
    public int getGenderCode() { 
        return genderCode; 
    } 
    /** Set the 'GenderCode' element value. */ 
    public void setGenderCode(int genderCode) { 
        this.genderCode = genderCode; 
    } 
}

移除未使用的定義

初始簡單 Schema 的第一個自訂範例中,透過 generate-all="false" 禁止產生每個全域定義,並用 includes 列表強制產生特定定義,控制模型包含的型別。清單 14 顯示加入這些屬性的修改後自訂,僅包含 TimeCard 元素(及其表示所需的一切)。

程式碼清單 14. 僅包含 TimeCard 組件的自訂

<schema-set xmlns:xs="https://www.w3.org/2001/XMLSchema" 
    package="org.hrxml.timecard" 
    type-substitutions="xs:integer xs:int" 
    generate-all="false"> 
    <name-converter strip-suffixes="Type"/> 
    <class-decorator class="org.jibx.schema.codegen.extend.CollectionMethodsDecorator"/> 
    <schema name="TimeCard.xsd" includes="TimeCard"/> 
</schema-set>

可用 custgen2 Ant 目標嘗試,或 custom2 執行完整流程。此變更將頂層類別數從 15 減至 10,是簡化模型的良好開端。

自訂個別組件

迄今僅看過在整組 Schema 或單一 Schema 應用的自訂範例。實際上也可自訂 CodeGen 處理 Schema 定義內的特定組件,包含全域定義及內嵌內容項。可用自訂包括從模型移除組件、變更組件使用的類別或值名稱、變更組件的 Schema 型別。

若能控制 Schema,直接修改 Schema 較簡單。但企業資料交換 Schema 常含專用組件,不適合特定應用,且 Schema 通常不在掌控中。此時自訂可簡化模型而不動原始 Schema。

組件自訂方式

將自訂元素與代表組件的 Schema 定義元素關聯。建立關聯有多種方法,因情況而異。但固定規則是:自訂元素名稱必須與 Schema 組件元素名稱相符。要自訂 <xs:element>,需使用 <element> 自訂元素(無命名空間)。

清單 15 顯示 TimeCard 參考的其他 Schema 之一定義,示範單一組件自訂。PersonNameType 包含多個 xs:string 元素及複雜結構元素。測試文件恰巧未包含 Affix 或 AlternateScript 實例,移除它們簡化模型再合適不過。

程式碼清單 15. PersonName Schema

<xsd:complexType name="PersonNameType"> 
<xsd:sequence> 
<xsd:element name="FormattedName" type="xsd:string" minOccurs="0"/> 
<xsd:element name="LegalName" type="xsd:string" minOccurs="0"/> 
<xsd:element name="GivenName" type="xsd:string" minOccurs="0" maxOccurs="unbounded"/> 
<xsd:element name="PreferredGivenName" type="xsd:string" minOccurs="0"/> 
<xsd:element name="MiddleName" type="xsd:string" minOccurs="0"/> 
<xsd:element name="FamilyName" minOccurs="0" maxOccurs="unbounded"> 
<xsd:complexType> ... </xsd:complexType> 
</xsd:element> 
<xsd:element name="Affix" minOccurs="0" maxOccurs="unbounded"> 
<xsd:complexType> ... </xsd:complexType> 
</xsd:element> 
<xsd:element name="AlternateScript" minOccurs="0" maxOccurs="unbounded"> 
<xsd:complexType> ... </xsd:complexType> 
</xsd:element> 
</xsd:sequence> 
<xsd:attribute name="script" type="xsd:string"/> 
</xsd:complexType>

程式碼清單 16. 直接自訂不需要的組件

<schema-set ...> 
<schema name="PersonName.xsd"> 
<element path="complexType[@name=PersonNameType]/sequence/element[@name=Affix]" ignore="true"/> 
<element path="complexType[@name=PersonNameType]/sequence/element[@name=AlternateScript]" ignore="true"/> 
</schema> 
</schema-set>

路徑類似 XPath 瀏覽 Schema 結構,步驟以斜線分隔,命名組件步驟可用 [@name=...] 斷言挑選特定實例。也可用通配符:* 匹配單一元素,** 匹配任意層級巢狀元素。但不能用 **/element[@name=Affix],CodeGen 要求明確識別涉及的全域定義組件,作為防止錯誤套用的保護。

只要巢狀匹配 Schema 結構,即可巢狀組件自訂。此時每個自訂只需指定相對於包含自訂的目標。也可用 name="..." 屬性替代路徑最後一步的 [@name=...],並省略最後一步元素名稱(必須與自訂元素名稱相同)。甚至可完全避免路徑,改用結合名稱屬性的巢狀。清單 17 顯示相同自訂的重構寫法:

程式碼清單 17. 巢狀自訂不需要的組件

<schema-set ...> 
<schema name="PersonName.xsd"> 
<complexType name="PersonNameType"> 
<sequence> 
<element name="Affix" ignore="true"/> 
<element name="AlternateScript" ignore="true"/> 
</sequence> 
</complexType> 
</schema> 
</schema-set>

簡化資料模型

除前節的 PersonName 組件外,TimeCard Schema 還有大量範例文件未使用的複雜組件。透過自訂移除未使用組件,可大幅簡化模型。某些情況下 CodeGen 產生的 Java 值名稱不夠直觀,尤其重複使用相同元素名稱導致只能靠數字後綴區分,難以正確使用。參見清單 10 範例,產生代碼包含 duration 和 duration1 兩個欄位。可用自訂改為更有意義的名稱。

清單 18 顯示 hrxml 目錄下的 custom3.xml,包含所有這些自訂,有意結合巢狀、路徑、帶名稱的路徑等多種識別組件方式。值名稱自訂在底部,用 value-name="simpleDuration" 將第二個 duration 名稱改為更具描述性的形式。

程式碼清單 18. 簡化 TimeCard 資料模型並使其含義更清晰

<schema-set xmlns:xs="https://www.w3.org/2001/XMLSchema" 
    xmlns:tns="https://ns.hr-xml.org/2007-04-15" 
    package="org.hrxml.timecard" 
    type-substitutions="xs:integer xs:int" 
    generate-all="false"> 
    <name-converter strip-suffixes="Type"/> 
    <class-decorator class="org.jibx.schema.codegen.extend.CollectionMethodsDecorator"/> 
    <schema name="UserArea.xsd" excludes="UserArea UserAreaType"/> 
    <schema name="PersonName.xsd"> 
    <complexType name="PersonNameType"> 
    <sequence> 
    <element name="Affix" ignore="true"/> 
    <element name="AlternateScript" ignore="true"/> 
    </sequence> 
    </complexType> 
    </schema> 
    <schema name="TimeCard.xsd" includes="TimeCard"> 
    <complexType name="TimeCardType"> 
    <element path="**/element[@name=Allowance]" ignore="true"/> 
    <element path="**/element[@name=PieceWork]" ignore="true"/> 
    <element path="**/element[@name=TimeEvent]/**/" name="RateOrAmount" ignore="true"/> 
    <element path="**/choice/[@name=Duration]" value-name="simpleDuration"/> 
    </complexType> 
    </schema> 
</schema-set>

可用 custgen3 Ant 目標嘗試,或 custom3 執行完整流程。自訂將產生類別數減至 9 個頂層類別與 10 個內部類別,共 19 個類別,僅為未自訂初始模型類別數的一半。

CodeGen 命令列參數

除教程代碼使用的參數外,CodeGen 尚支援其他命令列參數。表 1 列出最重要選項:

命令用途
-c path輸入自訂文件路徑
-n package無命名空間 Schema 定義的預設套件(預設為預設套件)
-p package所有 Schema 定義的預設套件(預設使用從各 Schema 命名空間產生的套件)
-s pathSchema 根目錄路徑(預設為當前目錄)
-t path產生輸出的目標目錄路徑(預設為當前目錄)
-v詳細輸出旗標
-w產生輸出前從目標目錄刪除所有文件(若目標目錄為當前目錄則忽略)

在自訂屬性值前加 -- 前綴,可將全域自訂作為命令列參數傳遞,無需建立自訂文件。如要設定清單 5 自訂的全域選項,命令列加入 --prefer-inline=true --show-schema=false --enumeration-type=simple --generate-all=false。此法無需引號,列表值以逗號分隔(如 --strip-suffixes=Type,Group)。

結語

本教程介紹使用 JiBX 透過 XML Schema 定義產生 Java 資料模型,並在符合 Schema 的文件與模型間雙向轉換,以及如何用自訂控制模型產生方式。除本教程介紹的自訂外,還有許多其他自訂選項控制模型各面向。JiBX 文件提供完整自訂選項資訊及更多 Schema 產生代碼範例。

Web 服務定義是 XML Schema 主要應用之一。JiBX 目前可用於 Apache Axis2、Apache CXF、XFire、Spring-WS 等 Web 服務堆疊,也支援自身的 JiBX/WS 輕量級引擎。可對任一堆疊使用本教程討論的 Schema 產生代碼功能,但目前仍需先從 WSDL 服務定義提取 Schema 定義,再進行產生,並執行各堆疊專屬額外步驟以獲得有效 Web 服務。JiBX 未來版本將簡化建立 Web 服務實作的過程,請參閱 JiBX 發行版文件了解最新功能。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved