|
作者:IT168 gmplayer 2007-04-01
引入 Java提供各種方式來(lái)處理XML,其中包括: 使用簡(jiǎn)單的文件I/O或者javax.xml.stream.XmlStreamWriter. 使用XML序列化java.beans.XMLEncoder,它能夠產(chǎn)生一個(gè)Java Bean的XML表示法,同樣,ObjectOutputStream也能夠用來(lái)創(chuàng)建序列化對(duì)象的二進(jìn)制表示法。 使用專(zhuān)門(mén)的類(lèi)庫(kù)像XStream,直接使用SAX(XML的簡(jiǎn)單API)或者通過(guò)JAXP API來(lái)使用DOM(文檔對(duì)象模型)。 盡管XML和Java技術(shù)已經(jīng)在數(shù)據(jù)交換上已經(jīng)有成熟的模型,但是將一個(gè)Java對(duì)象模型映射到XML和將XML映射為Java對(duì)象模型還是有點(diǎn)神秘的??梢钥紤]使用JAXB作為一種解決方案,JAXB (Java Architecture for XML Binding)可以使你將XML轉(zhuǎn)換為Java數(shù)據(jù)綁定和從XML schemas產(chǎn)生Java類(lèi),反之也是可以的。它非常方便且容易使用,它提供了像XML驗(yàn)證和使用注釋和適配器進(jìn)行定制。下圖闡述了JAXB的用法:

JAXB API在javax.xml.bind包中被定義,它是一系列的接口和類(lèi),從schema產(chǎn)生的代碼可以使應(yīng)用程序進(jìn)行通訊。JAXB API最主要的就是javax.xml.bind.JAXBContext類(lèi),JAXBContext是一個(gè)抽象的類(lèi),它可以管理XML/Java 綁定,也可以被看作為一個(gè)工廠(chǎng),因?yàn)樗峁?nbsp; Unmarshaller類(lèi)可以將XML轉(zhuǎn)換為Java變得連續(xù)并且可以隨意的驗(yàn)證XML(使用setSchema方法) Marshaller類(lèi)使一個(gè)對(duì)象圖形到XML的轉(zhuǎn)換變得連續(xù)并且可以隨意的驗(yàn)證。 首先,JAXB通過(guò)使用schema generator能夠在一個(gè)XML schema中定義一系列的類(lèi),它也提供相反的操作,允許你通過(guò)schema compiler從一個(gè)給定的XML schema產(chǎn)生Java類(lèi)的集合。 schema compile將XML schema看作為輸入并產(chǎn)生一個(gè)Java類(lèi)和接口的包,這個(gè)接口反應(yīng)了在源schema中定義的規(guī)則。這些類(lèi)是被注釋使用一個(gè)可定制的Java-XML映射提供運(yùn)行時(shí)框架。 JAXB也可以使用schema generator從一個(gè)XML schema中產(chǎn)生一個(gè)Java對(duì)象層或者提供一個(gè)對(duì)象Java層來(lái)描述相應(yīng)的XML schema。運(yùn)行時(shí)框架提供了相應(yīng)的unmarshalling, marshalling和驗(yàn)證功能。也就是說(shuō),你可以從一個(gè)XML文檔轉(zhuǎn)換為一個(gè)對(duì)象圖形(unmarshalling)或者將一個(gè)對(duì)象圖形轉(zhuǎn)換為XML格式(marshalling)。 這些功能就是為什么JAXB經(jīng)常和Web service相關(guān)聯(lián)的原因。Web service使用API來(lái)將對(duì)象轉(zhuǎn)換為消息,該消息可以通過(guò)SOAP來(lái)進(jìn)行發(fā)送。本文所使用的例子就是一個(gè)虛擬音樂(lè)公司的地址薄的應(yīng)用。
產(chǎn)生XML 音樂(lè)公司銷(xiāo)售它的音樂(lè)產(chǎn)品像樂(lè)器,唱片等,在它的地址薄中存儲(chǔ)著兩種類(lèi)型的客戶(hù):個(gè)體和公司。每一個(gè)客戶(hù)都有一個(gè)家庭地址和一系列的發(fā)貨地址。發(fā)貨地址可以是周末或早上有效,這些信息可以以標(biāo)簽的形式添加到地址薄中。其形式如下圖所示:

該公司想要以XML形式發(fā)送一些客戶(hù)的信息給合作伙伴,因此它需要一個(gè)給定客戶(hù)的對(duì)象模型的XML文檔。使用JAXB實(shí)現(xiàn)起來(lái)很容易。下列代碼創(chuàng)建了一個(gè)個(gè)體的實(shí)例并設(shè)置了他的屬性(first name ,last name)一個(gè)家庭地址,兩個(gè)發(fā)貨地址。對(duì)象都設(shè)置好以后使用javax.xml.bind.Marshaller來(lái)產(chǎn)生個(gè)體對(duì)象的XML表示。 Listing 1: Creates an XML Representation of an Individual
// Instantiates Tag objects
Tag tag1 = new Tag("working hours");
Tag tag2 = new Tag("week-ends");
Tag tag3 = new Tag("mind the dog");
// Instantiates an individual object with home address
calendar.set(1940, 7, 7, 0, 0, 0);
Individual individual = new Individual(1L, "Ringo", "Starr", "+187445", "ringo@star.co.uk", calendar.getTime());
individual.setHomeAddress(new Address(2L, "Abbey Road", "London", "SW14", "UK"));
// Instantiates a first delivery address
Address deliveryAddress1 = new Address(3L, "Findsbury Avenue", "London", "CE451", "UK");
deliveryAddress1.addTag(tag1);
deliveryAddress1.addTag(tag3);
individual.addDeliveryAddress(deliveryAddress1);
// Instantiates a second delivery address
Address deliveryAddress2 = new Address(4L, "Camden Street", "Brighton", "NW487", "UK");
deliveryAddress2.addTag(tag1);
deliveryAddress2.addTag(tag2);
individual.addDeliveryAddress(deliveryAddress2);
// Generates XML representation of an individual
StringWriter writer = new StringWriter();
JAXBContext context = JAXBContext.newInstance(Customer.class);
Marshaller m = context.createMarshaller();
m.marshal(individual, writer);
System.out.println(writer);

這段代碼使用靜態(tài)方法newInstance來(lái)產(chǎn)生JAXBContext的一個(gè)實(shí)例。創(chuàng)建Marshaller對(duì)象,然后調(diào)用marshal方法產(chǎn)生一個(gè)個(gè)體對(duì)象的XML表示,即StringWinter。
接下來(lái)要做的就是增加@XmlRootElement注釋到Customer類(lèi)中,@XmlRootElement注釋通知JAXB被注釋的類(lèi)是XML文檔的根元素。如果該注釋丟失,JAXB將拋出異常。如果增加了注釋并運(yùn)行程序?qū)?huì)得到下列XML文檔: Listing 2: XML Representation of an Individual
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<customer>
<deliveryAddresses>
<city>London</city>
<country>UK</country>
<id>3</id>
<street>Findsbury Avenue</street>
<tags>
<name>working hours</name>
</tags>
<tags>
<name>mind the dog</name>
</tags>
<zipcode>CE451</zipcode>
</deliveryAddresses>
<deliveryAddresses>
<city>Brighton</city>
<country>UK</country>
<id>4</id>
<street>Camden Street</street>
<tags>
<name>working hours</name>
</tags>
<tags>
<name>week-ends</name>
</tags>
<zipcode>NW487</zipcode>
</deliveryAddresses>
<email>ringo@star.co.uk</email>
<homeAddress>
<city>London</city>
<country>UK</country>
<id>2</id>
<street>Abbey Road</street>
<zipcode>SW14</zipcode>
</homeAddress>
<id>1</id>
<telephone>+187445</telephone>
</customer>
通過(guò)一個(gè)注釋@XmlRootElemen,一個(gè)Marshaller對(duì)象和異常產(chǎn)生的代碼,可以很容易的得到對(duì)象圖形的XML表示。根元素<customer>代表Customer對(duì)象,它包括所有的屬性(一個(gè)家庭地址,兩個(gè)發(fā)貨地址,一個(gè)ID,一個(gè)電話(huà)號(hào)碼等)。
定制XML文檔
音樂(lè)公司和他的商業(yè)伙伴對(duì)上面給出的XML文檔(Listing 2)并不完全滿(mǎn)意,他們可能拋棄某些信息(地址標(biāo)識(shí)符號(hào),tags)出生日期的格式,訂單的某些屬性等。由于有了javax.xml.bind.annotation包的注釋?zhuān)琂AXB提供了一種方式來(lái)定制和控制XML的結(jié)構(gòu)。
首先,如果你想拋棄<customer>元素而使用<individual>或者<company>來(lái)替代根元素。如果讓JAXB不使用抽象Customer類(lèi),可以放棄使用@XmlRootElement而使用@XmlTransient來(lái)產(chǎn)生臨時(shí)類(lèi)。
XML文檔是由一系列的元素(<element>value</element>)和屬性(<element attribute="value"/>)組成。JAXB使用兩種注釋來(lái)區(qū)分他們:@XmlAttribute 和 @XmlElement,每個(gè)注釋有一系列的參數(shù)可以對(duì)屬性進(jìn)行重命名,可以為空值,給定的一個(gè)默認(rèn)值等。下列代碼使用兩種注釋來(lái)將id轉(zhuǎn)換為XMl的屬性(而不是元素)并且重命名了發(fā)貨地址元素(將address改為deliveryAddress):
@XmlTransient
 public abstract class Customer ...{
@XmlAttribute
protected Long id;
protected String telephone;
protected String email;
protected Address homeAddress;
@XmlElementWrapper(name = "delivery")
@XmlElement(name = "address")
protected List<Address> deliveryAddresses = new ArrayList<Address>();
// Constructors, getters, setters
}
這段代碼使用了@XmlElementWrapper注釋?zhuān)a(chǎn)生包裝元素在發(fā)貨地址的外圍。再看Listing 2,有個(gè)<deliveryAddresses>元素,通過(guò)上面的代碼,就可以在<address>元素前加了<delivery>元素。 繼續(xù)討論地址,如果想要放棄標(biāo)識(shí)符和tags,可以使用@XmlTransient注釋。為了重命名一個(gè)元素,使用@XmlElement注釋的name屬性。下列代碼就對(duì)屬性zipcode重命名為<zip>元素:
 @XmlType(propOrder = ...{"street", "zipcode", "city", "country"})
@XmlAccessorType(XmlAccessType.FIELD)
 public class Address ...{
@XmlTransient
private Long id;
private String street;
private String city;
@XmlElement(name = "zip")
private String zipcode;
private String country;
@XmlTransient
private List<Tag> tags = new ArrayList<Tag>();
// Constructors, getters, setters
}
上面的@XmlType注釋可以將一個(gè)類(lèi)或者枚舉映射為一個(gè)XML schema類(lèi)型??梢允褂盟鼇?lái)指定一個(gè)命名空間或者使用propOrder屬性來(lái)定制屬性,按照這個(gè)定制可以列出屬性的名字和產(chǎn)生XML文檔。 Table 1顯示了XML文檔的三個(gè)不同的摘錄:
|
Default XML Representation
|
Annotated Customer Class
|
Annotated Address Class
|
|
<customer> <deliveryAddresses> <city>London</city> <country>UK</country> <id>3</id> <street>Findsbury</street> <tags> <name>working hours</name> </tags> <tags> <name>mind the dog</name> </tags> <zipcode>CE451</zipcode> </deliveryAddresses> <deliveryAddresses> <city>Brighton</city> <country>UK</country> <id>4</id> <street>Camden</street> <tags> <name>working hours</name> </tags> <tags> <name>week-ends</name> </tags> <zipcode>NW487</zipcode> </deliveryAddresses> (...) </customer>
|
<individual id="1"> <delivery> <address> <city>London</city> <country>UK</country> <id>3</id> <street>Findsbury</street> <tags> <name>working hours</name> </tags> <tags> <name>mind the dog</name> </tags> <zipcode>CE451</zipcode> </address> <address> <city>Brighton</city> <country>UK</country> <id>4</id> <street>Camden</street> <tags> <name>working hours</name> </tags> <tags> <name>week-ends</name> </tags> <zipcode>NW487</zipcode> </address> </delivery> (...) </individual>
|
<individual id="1"> <delivery> <address> <street>Findsbury</street> <zip>CE451</zip> <city>London</city> <country>UK</country> </address> <address> <street>Camden</street> <zip>NW487</zip> <city>Brighton</city> <country>UK</country> </address> </delivery> (...) </individual>
|
也可以注釋具體的類(lèi)Company和Individual來(lái)定制映射。首先作為XML文檔的根元素,不得不使用@XmlRootElement注釋來(lái)指定XML命名空間 http://www.watermelon.example/customer。該例子中使用@XmlType.propOrder來(lái)定制屬性??梢允褂脧某?lèi)Customer中繼承像id,email,telephone,homeAddress等。
|
Annotated Company Class
|
Annotated Individual Class
|
|
@XmlRootElement(name = "company", namespace=
"http://www.watermelon.example/customer") @XmlType(propOrder = {"id", "name", "contactName", "telephone", "email", "numberOfEmployees", "homeAddress", "deliveryAddresses"}) @XmlAccessorType(XmlAccessType.FIELD) public class Company extends Customer {
@XmlAttribute private String name; private String contactName; private Integer numberOfEmployees; // Constructors, getters, setters }
|
@XmlRootElement(name = "individual", namespace = "http://www.watermelon.example/customer") @XmlType(propOrder = {"id", "lastname", "firstname", "dateOfBirth", "telephone", "email", "homeAddress", "deliveryAddresses"}) @XmlAccessorType(XmlAccessType.FIELD) public class Individual extends Customer {
private String firstname; @XmlAttribute private String lastname; @XmlJavaTypeAdapter(DateAdapter.class) private Date dateOfBirth; // Constructors, getters, setters }
|
JAXB映射java.util.Date屬性為默認(rèn)值,例如,個(gè)體的出生日期將顯示為下列格式:<dateOfBirth>1940-08-07T00:00:00.781+02:00</dateOfBirth> 為了格式化日期(如:07/08/1953),有兩種選擇: 1. 使用日期類(lèi)型javax.xml.datatype.XMLGregorianCalendar而不使用java.util.Date。 2. 使用一個(gè)適配器,就像上面代碼看到的那樣,個(gè)體類(lèi)Individual使用@XmlJavaTypeAdapter注釋。@XmlJavaTypeAdapter(DateAdapter.class)通知JAXB使用適配器調(diào)用DateAdapter當(dāng)marshalling/unmarshalling屬性dateOfBirth時(shí)。 寫(xiě)一個(gè)類(lèi)(DateAdapter)來(lái)繼承XmlAdapter。覆蓋marshal 和 unmarshal方法。這種方法可以將日期按照一定格式的字符串進(jìn)行格式化,反之亦然。下列代碼使用java.text.SimpleDateFormat來(lái)格式化日期:
 public class DateAdapter extends XmlAdapter<String, Date> ...{
DateFormat df = new SimpleDateFormat("dd/MM/yyyy");
 public Date unmarshal(String date) throws Exception ...{
return df.parse(date);
}
 public String marshal(Date date) throws Exception ...{
return df.format(date);
}
}
現(xiàn)在返回Listing 1,如果Marshaller.marshal()方法被調(diào)用,DateAdapter.marshal()也被調(diào)用,出生日期也被格式化了.下面是獲得的XML文檔:
|
Individual XML Document
|
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:individual lastname="Starr" id="1" xmlns:ns2="http://www.watermelon.example/customer"> <firstname>Ringo</firstname> <dateOfBirth>07/08/1940</dateOfBirth> <telephone>+187445</telephone> <email>ringo@star.co.uk</email> <homeAddress> <street>Abbey Road</street> <zip>SW14</zip> <city>London</city> <country>UK</country> </homeAddress> <delivery> <address> <street>Findsbury Avenue</street> <zip>CE451</zip> <city>London</city> <country>UK</country> </address> <address> <street>Camden Street</street> <zip>NW487</zip> <city>Brighton</city> <country>UK</country> </address> </delivery> </ns2:individual>
|
|
Company XML Document
|
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <ns2:company name="Sony" id="1" xmlns:ns2="http://www.watermelon.example/customer"> <contactName>Mr Father</contactName> <telephone>+14519454</telephone> <email>contact@sony.com</email> <numberOfEmployees>25000</numberOfEmployees> <homeAddress> <street>General Alley</street> <zip>75011</zip> <city>Paris</city> <country>FR</country> </homeAddress> <delivery> <address> <street>St James St</street> <zip>SW14</zip> <city>London</city> <country>UK</country> </address> <address> <street>Central Side Park</street> <zip>7845</zip> <city>New York</city> <country>US</country> </address> </delivery> </ns2:company>
|
( 完 )
|