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 組件總數少。稍後將示範如何透過自訂進一步簡化模型。
程式碼清單 8 展示 CodeGen 如何處理 TimeCardType 中兩個元素的選擇結構。預設情況下,CodeGen 使用一個選擇變數追蹤目前作用中的選項。選項包含的值的 setter 允許寫入新值,但無法直接切換選項(嘗試會拋出 IllegalStateException)。若需在設定後變更選項,必須先呼叫清除方法(如 clearReportedResourceSelect())重置狀態。
/**
* 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 依狀態執行不同邏輯。這兩屬性可在任意自訂層級使用,便於統一設定行為並保留個別調整彈性。
企業 Schema 常用 <xs:any> 建立擴充點。預設 CodeGen 以 org.w3c.dom.Element 物件(maxOccurs > 1 時用 List)處理,提供處理任意符合定義文件的彈性。程式碼清單 9 展示對應清單 7 Schema 片段的產生代碼,因 maxOccurs="unbounded" 而使用 Element 列表。
/**
* ...
* 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 表示,執行期型別對應全域定義。
同樣地,CodeGen 忽略或部分處理 <xs:simpleType> 定義的多個面向。<xs:simpleType> 限制(長度、範圍、正則)中,僅 <xs:enumeration> 在產生模型中強制執行。<xs:simpleType> <union> 也被忽略。程式碼清單 10 顯示對應 <xs:union> 參考的產生代碼與原始 Schema 片段,所有指向 union 型別的參考(如 TimeCardDuration、AnyDateTimeType)在代碼中均以簡單 String 表示。
/**
* 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>
比較清單 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)。
/**
* 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>
<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 原生型別。
/**
* 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>
* <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 元素(及其表示所需的一切)。
<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 實例,移除它們簡化模型再合適不過。
<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>
<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 顯示相同自訂的重構寫法:
<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 名稱改為更具描述性的形式。
<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 尚支援其他命令列參數。表 1 列出最重要選項:
| 命令 | 用途 |
|---|---|
| -c path | 輸入自訂文件路徑 |
| -n package | 無命名空間 Schema 定義的預設套件(預設為預設套件) |
| -p package | 所有 Schema 定義的預設套件(預設使用從各 Schema 命名空間產生的套件) |
| -s path | Schema 根目錄路徑(預設為當前目錄) |
| -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 發行版文件了解最新功能。


