標題:XMLBeans事例 作者:John Methot

標題:XMLBeans事例
2003-03-21
作者:John Methot

XMLBeans介紹

XML已經迅速成為網路上交易處理的通用語言(lingua franca)。它是Web服務概念的基礎,並且被廣泛地應用在電子檔案的交換中。用於處理Java應用中XML的第一代工具,基於檔案物件模型(DOM)、XML的簡單API(SAX)及XML解析器,分別完成各自的任務。然而,第一代技術沒有充分利用Java語言的靈活性和強大性。

BEA Systems最近推出了一項叫做XMLBeans的新技術,它為在Java中處理XML提供更自然的、更直觀的、更有力的處理機制。XMLBeans的名字是由XML(這很明顯)加上JavaBeans得出來的,因為XMLBeans借用了JavaBeans元件體系結構中流行的屬性樣式。

當XMLBeans的8.0版本在2003年發佈後,它的使用將貫穿整個WebLogic平臺。但是這項技術本身能夠單獨運行,並且BEA已經發佈了一項Web服務,允許在你選擇的XML模式中試驗XMLBeans。你提交模式(.xsd)檔(或是包含多個相關模式的ZIP檔),然後Web服務返回一個JAR檔,這個JAR檔包含針對模式的XMLBeans類以及支援XMLBeans API。稍後,我將在本文中演示怎樣使用這項服務。你可以在BEA dev2dev開發者網站XMLBeans Technical Track中閱讀XMLBeans Service

我在文中給出的例子論證了XMLBeans的一個獨特的特徵:XMLBeans模式編譯器專為你的模式生成的強類型模式類型系統API。XMLBeans也被用於無模式的方案操作中。為得到更多的關於XMLBeans性能的內容,請參見XMLBeans Overview Page

什麼是XML模式?

雖然XML模式可以用於建立內容任意的XML檔案,但它更感興趣的(而且必需)是定義特定類型的檔案。例如,如果每個公司都能為購買訂單定義它自己的電子檔案格式,電子商業將不會走得很遠。XML支援正式檔案類型的定義,在這些類型的基礎上可以驗證其他任何特定的檔案。
在XML還不成熟的時候,有人在檔案類型定義(DTD)檔中定義了一個檔案類型。在DTD檔案中,你可以指定能夠存在於事例檔案中的元素類型,元素必須被安排好,對於數值元素來說,還必須有值的範圍樣的限制。但是DTD檔案也有許多缺點,最大的缺點是DTD檔案用一種離奇的語言而不是用XML語言本身來表達。

現在定義XML檔案的方法是使用XML模式。XML模式的用途和DTD檔案一樣,只不過是它用XML語言表達的。這意味著你可以用標準的XML工具建立、編輯以及操縱XML模式。

XML模式規範(可以在http://www.w3.org/XML/Schema中找到)還定義了一列可以用模式表達的46種特定資料類型,其中包括字串、整型、浮點型、日期等更多的類型。因此,XML模式可以利用豐富的類型系統來反映了現代編程語言中可用的新技術。

本文描述的例子使用了兩種類型的XML檔案:聯繫人(contact)和地址簿(address book)。其中位址簿可以包含聯繫人。

contactUsa.xsd模式

XML模式通常存放在副檔名為.xsd的檔中,這是由於XML模式定義(XML Schema Definition)的首字母縮寫為xsd。下面是一個來自contactUsa.xsd(本文提到的所有檔在AddressBookApp.zip中都可以找到)檔的XML模式例子。這個模式為位址簿中的條目(聯繫人)定義了格式。為簡單起見,這個模式中用到的郵政位址都是按照美國的習慣表示的。

<xs:schema targetNamespace="http://dearjohn/address-book"
xmlns:xs=http://www.w3.org/2001/XMLSchema
xmlns:address-book="http://dearjohn/address-book"
elementFormDefault="qualified">
<xs:element name="contact">
<xs:complexType>
<xs:sequence>
<xs:element name="family-name" type="xs:string"/>
<xs:element name="given-name" type="xs:string"/>
<xs:element ref="address-book:mailing-address" minOccurs="0"
maxOccurs="2"/>
<xs:element ref="address-book:phone-number" minOccurs="0"
maxOccurs="3"/>
</xs:sequence>
</xs:complexType>
</xs:element>

<xs:element name="mailing-address">
<xs:complexType>
<xs:sequence>
<xs:element name="address-line-1" type="xs:string"/>
<xs:element name="address-line-2" type="xs:string"
minOccurs="0"/>
<xs:element name="city" type="xs:string"/>
<xs:element name="state" type="address-book:state"/>
<xs:element name="zipcode" type="address-book:zipcode"/>
</xs:sequence>
<xs:attribute name="location" type="xs:string"/>
</xs:complexType>
</xs:element>

<xs:element name="phone-number">
<xs:complexType>
<xs:sequence>
<xs:element name="area-code"
type="address-book:area-code"/>
<xs:element name="local-phone-number"
type="address-book:local-phone-number"/>
</xs:sequence>
<xs:attribute name="location" type="xs:string"/>
</xs:complexType>
</xs:element>

<xs:simpleType name="area-code">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{3}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="local-phone-number">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{3}-[0-9]{4}"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="state">
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="AL"/>
<xs:enumeration value="AK"/>
...
<xs:enumeration value="WI"/>
<xs:enumeration value="WY"/>
</xs:restriction>
</xs:simpleType>

<xs:simpleType name="zipcode">
<xs:restriction base="xs:string">
<xs:pattern value="[0-9]{5}(-[0-9]{4})?"/>
</xs:restriction>
</xs:simpleType>

</xs:schema>


在XML方案中有兩種基本類型的元素:簡單類型和複雜類型。簡單類型定義的元素有值但沒有子元素,複雜類型定義的元素可以有子元素。在上面的模式中,<contact>被定義為一個複雜元素,該元素可以(在本模式中實際上必須)有子元素<family-name>和<given-name>,還可以有類型<mailing-address>和<phone-number>的子類型。

我不想在XML模式上用太多的篇幅,它是一個複雜的主題而且關於這個主題有許多好書可以利用。但是這裏有一些關於定義在contactUsa.xsd中模式的聲明,即使你不熟悉XML模式,它也有助於你理解。下面是這些聲明:

  • contactUsa.xsd中定義的所有元素都在XML名字空間http://dearjohn/address-book中。targetNamespace聲明定義了用於該模式(在schema-speak中叫做"全局"元素)中頂級元素的命名空間。
  • elementFormDefault="qualified"規範意味著本方案中的所有非全局性元素也在目標命名空間中(只要它們沒有明確說明位於其他的命名空間)。在模式檔中指定elementFormDefault="qualified"幾乎總是一個好主意:你一般不想讓同一個模式中的全局性和非全局性元素處於不同的命名空間。
  • <contact>總是包括名稱欄位,並且可以包含最多兩個<mailing-address>元素和最多三個<phone-number>元素,這是可選的。
  • <mailing-address>必須總是按照順序包含<address-line-1>、<city>、<state>和<zipcode>子元素。<mailing-address>可以在子元素<address-line-1>後緊跟一個子元素<address-line-2>。
  • <mailing-address>和<phone-number>元素都有位置屬性,然而在這個模式中沒有表示出來,目的是為了讓位置容納類似於"home"和"work"這樣的值。
  • 所有<area-code>、<local-phone-number>和<zipcode>元素的值被限制為特定的文本樣式。
  • 所有<state>元素的值被限制在50個州的縮寫加上哥倫比亞區的列舉範圍內。

下麵的XML檔案遵照的是contactUsa.xsd模式:

<?xml version="1.0"?>
<contact xmlns="http://dearjohn/address-book">
<family-name>Smithers</family-name>
<given-name>Bill</given-name>
<mailing-address location="home">
<address-line-1>1500 Dexter Ave.</address-line-1>
<city>Seattle</city>
<state>WA</state>
<zipcode>98109</zipcode>
</mailing-address>
<mailing-address location="work">
<address-line-1>501 Pike St.</address-line-1>
<address-line-2>Suite 2900</address-line-2>
<city>Seattle</city>
<state>WA</state>
<zipcode>98101</zipcode>
</mailing-address>
<phone-number location="home">
<area-code>206</area-code>
<local-phone-number>441-1695</local-phone-number>
</phone-number>
<phone-number location="mobile">
<area-code>206</area-code>
<local-phone-number>778-9218</local-phone-number>
</phone-number>
</contact>


addressBook.xsd模式

除了上面描述的contactUsa.xsd模式外,文中後面給出的位址簿的例子使用在addressBook.xsd中定義的如下模式:

<xs:schema targetNamespace="http://dearjohn/address-book"
xmlns:xs=http://www.w3.org/2001/XMLSchema
xmlns:address-book="http://dearjohn/address-book"
elementFormDefault="qualified">
<xs:include schemaLocation="contactUsa.xsd"/>
<xs:element name="address-book">
<xs:complexType>
<xs:sequence>
<xs:element ref="address-book:contact" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>

這個模式非常簡單:它定義了一個<address-book>元素,此元素可以包括0或來自contactUsa.xsd模式的更多<contact>元素。

用XMLBeans服務生成XMLBeans

既然已經定義了這些模式,就可以使用線上XMLBeans服務來針對模式生成XMLBeans類型系統(Java API)。

由於有兩個相關的模式檔,那麼我可以把它們壓縮到一個ZIP檔中去,以便上傳。然後在XMLBeans服務Schema Upload Page中的"要上傳的模式或ZIP檔"域指定ZIP檔。服務編譯完模式之後,XMLBeans服務下載頁就出現了,從這裏我可以下載包括我的XMLBeans類型的JAR檔。預設檔案名是xsdTypes.jar,但是在下載過程中,我將它重命名為bookTypes.jar。另外,我需要包含通用的XMLBeans支援原始碼的xmlbeans.jar文件。

那就它的全部東西了。你提供一個或多個模式,XMLBeans服務就返回一個包含你的類型的JAR檔。現在我準備建立一個位址簿應用,該應用處理來自於Java的XML地址簿和聯繫人。

AddressBookApp應用

AddressBookApp Java應用是一個簡單的命令行應用。它提供了一個具有如下選項的功能表:
1. 打開地址簿
2. 列印所有聯繫人姓名
3. 列印所有聯繫人
4. 列印聯繫人
5. 根據姓名查找聯繫人
6. 根據地區碼查找聯繫人
7. 根據州查找聯繫人
8. 從文件中添加聯繫人
9. 刪除聯繫人
10.保存位址簿
11.退出

我將講述上述這些功能的亮點,並利用原始碼片段來解釋XMLBeans是如何被用來實現這些操作的。有些操作我沒有提到,因為多個"列印"和"查找"操作彼此之間非常相似。


打開地址簿

此項操作的核心位於AddressBookApp的loadAddressBookFile方法中,其中一些原始碼如下所示:

File bookFile = null;
AddressBookDocument doc = null;
AddressBook book = null;

bookFile = new File(bookFilename);
doc = (AddressBookDocument) XmlLoader.load(bookFile);
book = doc.getAddressBook();


XmlLoader是基礎XMLBeans類之一,它的載入方法是可以將XML檔案讀入XMLBeans類型系統的方式中的一種,利用這種方法可以通過XMLBeans API操作XML。上面的原始碼展示了一種使用XMLBeans載入XML檔案的典型模式:呼叫XmlLoader的一個方法並且把結果強制轉換到你正在載入的檔案類型。在本例中是AddressBookDocument,這種類型代表一個與addressBook.xsd模式一致的XML檔案。
當我第一次使用XMLBeans時,經常在這個地方遇到ClassCastException運行時異常的問題。這個異常總是意味著一種情況:指定的檔案不是一個有效的模式事例,強制轉換的目標類型(在我的例子中是AddressBookDocument)就是在從這裏生成的。如果你遇到過這種錯誤,不要逐個模式地關注你嘗試載入的事例檔案的結構。要特別注意在模式和事例檔案兩者中的命名空間聲明。利用像XML Spy這樣的第三方模式敏感(schema-aware)XML工具非常有用。

一旦檔案被成功地載入和強制轉換,我就可以只呼叫AddressBookDocument的getAddressBook,來獲得表示檔中<address-book>元素的XMLBean。我獲得的AddressBook Java物件是應用程式中所有其他操作執行的物件。

注意,當我使用基於檔的方法載入XML檔案時,XMLBeans也提供了從流中載入XML檔案的方法。

列印所有聯繫人姓名

該操作呼叫AddressBookApp的 printAddressBookNames的方法,核心原始碼如下所示:

Contact contact = null;
int numRecs;
int i;

numRecs = book.sizeOfContactArray();
for (i=0; i<numRecs; i++)
{
contact = book.getContactArray(i);
System.out.println((i+1) + ": " +
getFormattedContactName(contact));
}

Contact是表示<contact>元素的XMLBeans Java類型。我呼叫AddressBook類的 sizeOfContactArray方法來找出有多少<contact>元素以<address-book>元素的子元素存在,其中<address-book>元素由book變數表示。然後對所有<contact>元素都照此辦理,為每個聯繫人列印AddressBookApp的getFormattedContactName方法的結果。getFormattedContactName包括下列原始碼:

String familyName = contact.getFamilyName();
String givenName = contact.getGivenName();

return
new String(
((familyName == null) ? "[no family-name]" : familyName) +
", " +
((givenName == null) ? "[no given-name]" : givenName));

最重要的部分是頭兩行,在這兩行裏我呼叫Contact類的getFamilyName和getGivenName方法來獲得<contact>元素的子元素<family-name> 和 <given-name>字串的值。

聯繫人的列表在列印之前沒有保存,把它留下給使用者作為練習用。我可以給出一點提示,即XMLBeans的XQuery能力對於實現這一目的很有用。

希望你已經開始意識到XMLBeans是很直觀的。生成的Java類型和在模式中定義的XML元素有同樣的名稱,這一點僅限於"java-fied"中。如果你熟悉XML檔案的結構,那麼就不難理解Java中XML檔案的結構了。

根據名字查找聯繫人

這步操作會提示使用者輸入搜尋字串,然後列印出所有<address-book>中<contact>中的名字,在<contact>中包含了搜尋字串。原始碼如下:

inputStr = getStringInput("Enter search string:").toLowerCase();
if (inputStr != null)
{
numContacts = _currentBook.sizeOfContactArray();
for (i=0; i<numContacts; i++)
{
contact = _currentBook.getContactArray(i);
if (getFormattedContactName(contact).
toLowerCase().
indexOf(inputStr) != -1)
{
System.out.println((i+1) + ": " +
getFormattedContactName(contact));
}
}
}

這段原始碼非常簡單。通過迴圈掃描<address-book>中的所有<contact>,檢查對應各個聯繫人的getFormattedContactName返回的字串是否包含了要搜尋的那個字串。這裏之所以要使用getFormattedContactName函數,是因為這個函數可以方便地返回既包含了<family-name>元素又包含了<given-name>元素的字串。注意,XMLBeans包含了XQuery功能,這樣就可以使所有"查找"操作快速執行,不過,這些我要留到其他文章裏講。

從文件中添加聯繫人

這步操作會提示使用者XML的檔案名應該符合contactUsa.xsd模式,然後呼叫AddressBookApp的loadContactFile方法,這個方法和上面在"打開位址簿"部分所描述的loadAddressBookFile方法非常相似。

當新的<contact>元素被成功地載入到一個Contact XMLBeans物件中時,AddressBookApp的addNewContact方法會將其添加到<address-book>中,原始碼如下:

int numRecs;
int i;
MailingAddress addr, newAddr;
PhoneNumber phoneNum, newPhoneNum;

Contact newContact = _currentBook.addNewContact();
newContact.setFamilyName(contact.getFamilyName());
newContact.setGivenName(contact.getGivenName());

numRecs = contact.sizeOfMailingAddressArray();
for (i=0; i<numRecs; i++)
{
newAddr = contact.getMailingAddressArray(i);
addr = newContact.addNewMailingAddress();
if (newAddr.isSetLocation())
{
addr.setLocation(newAddr.getLocation());
}
if (newAddr.getAddressLine1() != null)
{
addr.setAddressLine1(newAddr.getAddressLine1());
}
... more of the same for other child elements
}
numRecs = contact.sizeOfPhoneNumberArray();
for (i=0; i<numRecs; i++)
{
newPhoneNum = contact.getPhoneNumberArray(i);
phoneNum = newContact.addNewPhoneNumber();
if (newPhoneNum.isSetLocation())
{
phoneNum.setLocation(newPhoneNum.getLocation());
}
if (newPhoneNum.getAreaCode() != null)
{
phoneNum.setAreaCode(newPhoneNum.getAreaCode());
}
if (newPhoneNum.getLocalPhoneNumber() != null)
{
phoneNum.setLocalPhoneNumber(
newPhoneNum.getLocalPhoneNumber());
}
}

這段原始碼用於替代XMLBeans的一個特性,在寫這篇文章的時候,該特性還沒有實現(XMLBeans當前還只有測試版的原始碼)。不過原始碼倒是很是簡單:只是遍曆樹並傳送來自元素及其屬性的值,通過下面一行原始碼就可以最終實現這步操作:
book.setContact(newContact);

刪除聯繫人

這是所有操作中最簡單的操作。首先提示使用者需要刪除<contact>的索引,接著呼叫_currentBook.removeContact(index-1)函數,該函數將從<address-book>元素中刪除指定的<contact>子元素。這裏之所以要將使用者提供索引減1,是因為為了方便起見,應用程式的使用者介面的索引是基於1的。 保存位址簿

最後一步操作將<address-book>元素的當前狀態寫入到一個由使用者提供名稱的檔中。下麵是相應的原始碼:

File bookFile = new File(inputStr);
PrintStream destStream =
new PrintStream(new FileOutputStream(bookFile),
false, "UTF-8");
destStream.println("");

HashMap propMap = new HashMap();
propMap.put(XmlOptions.SAVE_PRETTY_PRINT, null);
propMap.put(XmlOptions.SAVE_PRETTY_PRINT_INDENT, new Integer(2));
String xmlText = _currentBook.xmlText(propMap);

destStream.println(xmlText);
destStream.close();

這裏我使用了一般的Java IO類建立了一個輸出流,以後,XML檔案將要寫入到這個輸入流中。注意,這裏我指定了必須用編碼("UTF-8")編寫這個檔案。XML檔案的符號編碼是個比較複雜的話題,所有這些都牽涉到國際化的問題。UTF是用於一種通用的安全的編碼方式。

接下來列印<?xml?>聲明,這些聲明都寫在每個XML檔案的開頭,沒有順序要求。

下一步是生成實際的XML文本,該文本將被寫入檔中。為完成這步操作,這裏呼叫了XmlObject(所有XMLBeans類的基類)的 xmlText方法。xmlText方法通過選項的選項對應來控制它的行為。我使用SAVE_PRETTY_PRINT選項來請求人類可識別的格式化,並指定了SAVE_PRETTY_PRINT_INDENT選項,表示我希望有多種層級的XML結構以供選擇,而不僅僅是只有兩種選擇。

最後,將XML文本寫入到輸出檔,接著關閉該檔。

編譯AddressBookApp應用程式

運行應用程式是非常簡單的:

javac -classpath "bookTypes.jar;xmlbeans.jar" address\AddressBookApp.java

(顯然,在Unix/Linux中,符號"\"應改為"/")

在類路徑中,惟一要添加的就是bookTypes.jar(即包含了按照我自己的模式生成的類型的JAR檔)和xmlbeans.jar(即包含了一般XMLBean支援的原始碼的JAR檔)。

注意,XMLBeans需要Java 2, Standard Edition (J2SE) 1.4環境的支援。


運行AddressBookApp應用程式
運行app也很簡單:

java -cp ".;./bookTypes.jar;./xmlbeans.jar" address.AddressBookApp

 

ZIP檔裏包含了事例原始碼,同時還包含了四個contact XML示例檔和兩個示例地址名冊:testbook.xml和fullbook.xml。testbook.xml僅包含在contactSmithers.xml中定義的聯繫人,fullbook.xml包含所有四個示例聯繫人。這些檔給了你足夠的資料,通過這些資料你可以對應用程式中的所有可用操作進行試驗。

結論

相信這個事例已經向我們展示了用XMLBeans處理XML是多麼簡單。我鼓勵你下載事例原始碼並進行試驗。然後用你自己的模式試一試。 我特意使這個事例相對簡單一些,而更多地關注XMLBean根據你自己的模式所生成的特定模式的API。關於XMLBeans還有更多的內容,比如它可以操縱無相關模式的XML檔案,可以使用XQuery在XML上執行複雜搜尋和變換,還有其他一些功能。

原文URL:http://dev2dev.bea.com/articles/DJ_003.jsp

 

你可能感兴趣的:(bean)