使用JAXB從一個(gè)對(duì)象模型中產(chǎn)生XML文檔 [
http://tech.it168.com/j/2007-04-01/200704011813902.shtml ]
作者: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)制表示法。
使用專門的類庫(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類,反之也是可以的。它非常方便且容易使用,它提供了像XML驗(yàn)證和使用注釋和適配器進(jìn)行定制。下圖闡述了JAXB的用法:
JAXB API在javax.xml.bind包中被定義,它是一系列的接口和類,從schema產(chǎn)生的代碼可以使應(yīng)用程序進(jìn)行通訊。JAXB API最主要的就是javax.xml.bind.JAXBContext類,JAXBContext是一個(gè)抽象的類,它可以管理XML/Java 綁定,也可以被看作為一個(gè)工廠,因?yàn)樗峁?div style="height:15px;">
Unmarshaller類可以將XML轉(zhuǎn)換為Java變得連續(xù)并且可以隨意的驗(yàn)證XML(使用setSchema方法)
Marshaller類使一個(gè)對(duì)象圖形到XML的轉(zhuǎn)換變得連續(xù)并且可以隨意的驗(yàn)證。
首先,JAXB通過(guò)使用schema generator能夠在一個(gè)XML schema中定義一系列的類,它也提供相反的操作,允許你通過(guò)schema compiler從一個(gè)給定的XML schema產(chǎn)生Java類的集合。
schema compile將XML schema看作為輸入并產(chǎn)生一個(gè)Java類和接口的包,這個(gè)接口反應(yīng)了在源schema中定義的規(guī)則。這些類是被注釋使用一個(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è)公司銷售它的音樂(lè)產(chǎn)品像樂(lè)器,唱片等,在它的地址薄中存儲(chǔ)著兩種類型的客戶:個(gè)體和公司。每一個(gè)客戶都有一個(gè)家庭地址和一系列的發(fā)貨地址。發(fā)貨地址可以是周末或早上有效,這些信息可以以標(biāo)簽的形式添加到地址薄中。其形式如下圖所示:
該公司想要以XML形式發(fā)送一些客戶的信息給合作伙伴,因此它需要一個(gè)給定客戶的對(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類中,@XmlRootElement注釋通知JAXB被注釋的類是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è)電話號(hào)碼等)。
定制XML文檔
音樂(lè)公司和他的商業(yè)伙伴對(duì)上面給出的XML文檔(Listing 2)并不完全滿意,他們可能拋棄某些信息(地址標(biāo)識(shí)符號(hào),tags)出生日期的格式,訂單的某些屬性等。由于有了javax.xml.bind.annotation包的注釋,JAXB提供了一種方式來(lái)定制和控制XML的結(jié)構(gòu)。
首先,如果你想拋棄<customer>元素而使用<individual>或者<company>來(lái)替代根元素。如果讓JAXB不使用抽象Customer類,可以放棄使用@XmlRootElement而使用@XmlTransient來(lái)產(chǎn)生臨時(shí)類。
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注釋,它產(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è)類或者枚舉映射為一個(gè)XML schema類型。可以使用它來(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>
也可以注釋具體的類Company和Individual來(lái)定制映射。首先作為XML文檔的根元素,不得不使用@XmlRootElement注釋來(lái)指定XML命名空間
http://www.watermelon.example/customer。該例子中使用@XmlType.propOrder來(lái)定制屬性??梢允褂脧某怌ustomer中繼承像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. 使用日期類型javax.xml.datatype.XMLGregorianCalendar而不使用java.util.Date。
2. 使用一個(gè)適配器,就像上面代碼看到的那樣,個(gè)體類Individual使用@XmlJavaTypeAdapter注釋。@XmlJavaTypeAdapter(DateAdapter.class)通知JAXB使用適配器調(diào)用DateAdapter當(dāng)marshalling/unmarshalling屬性dateOfBirth時(shí)。
寫(xiě)一個(gè)類(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>
Unmarshal和產(chǎn)生Schema
如圖1所示,JAXB可以用來(lái)unmarshal,產(chǎn)生和編譯一個(gè)schema,也就是用先前獲得的XML文檔來(lái)產(chǎn)生對(duì)象圖表。首先得到一個(gè)JAXBContext,創(chuàng)建一個(gè)Unmarshaller對(duì)象,調(diào)用unmarshal方法,然后返回Individual的屬性及他的一個(gè)實(shí)例:
// xmlString contains the XML document of an individual
StringReader reader = new StringReader(xmlString);
JAXBContext context = JAXBContext.newInstance(Individual.class);
Unmarshaller u = context.createUnmarshaller();
Individual individual = (Individual) u.unmarshal(reader);
System.out.println(individual.getFirstname());
一個(gè)XML schema描述了XML文檔的結(jié)構(gòu),用XML語(yǔ)法來(lái)寫(xiě)的。如果你對(duì)XML schema了解的不多,你也可以使用Sun的JAXB實(shí)現(xiàn)提供的schemaGen工具來(lái)產(chǎn)生一個(gè)XML schema。如Listing 3,可以看到Address, Company, Individual和 Tag類被描述為復(fù)雜的類型:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="address">
<xs:sequence>
<xs:element name="street" type="xs:string" minOccurs="0"/>
<xs:element name="zip" type="xs:string" minOccurs="0"/>
<xs:element name="city" type="xs:string" minOccurs="0"/>
<xs:element name="country" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="company">
<xs:sequence>
<xs:element name="contactName" type="xs:string" minOccurs="0"/>
<xs:element name="telephone" type="xs:string" minOccurs="0"/>
<xs:element name="email" type="xs:string" minOccurs="0"/>
<xs:element name="numberOfEmployees" type="xs:int" minOccurs="0"/>
<xs:element name="homeAddress" type="address" minOccurs="0"/>
<xs:element name="delivery" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="address" type="address" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" type="xs:long"/>
<xs:attribute name="name" type="xs:string"/>
</xs:complexType>
<xs:complexType name="individual">
<xs:sequence>
<xs:element name="firstname" type="xs:string" minOccurs="0"/>
<xs:element name="dateOfBirth" type="xs:string" minOccurs="0"/>
<xs:element name="telephone" type="xs:string" minOccurs="0"/>
<xs:element name="email" type="xs:string" minOccurs="0"/>
<xs:element name="homeAddress" type="address" minOccurs="0"/>
<xs:element name="delivery" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="address" type="address" minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
<xs:attribute name="id" type="xs:long"/>
<xs:attribute name="lastname" type="xs:string"/>
</xs:complexType>
<xs:complexType name="tag">
<xs:sequence>
<xs:element name="addresses" type="address" nillable="true"
minOccurs="0" maxOccurs="unbounded"/>
<xs:element name="name" type="xs:string" minOccurs="0"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="dateAdapter">
<xs:complexContent>
<xs:extension base="xmlAdapter">
<xs:sequence/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="xmlAdapter" abstract="true">
<xs:sequence/>
</xs:complexType>
</xs:schema>
如果下載JAXB,schema編譯器(xjc)也會(huì)被一同下載。
持久化注釋
如果你認(rèn)為JAXB可以將數(shù)據(jù)持久化到XML。JPA則是和它差不多的關(guān)系數(shù)據(jù)庫(kù)的術(shù)語(yǔ),事實(shí)上,二者都是依靠的注釋,這就意味著同樣的類都可以被JPA和JAXB注釋,按照這種說(shuō)法,可以提供一個(gè)XML表示也當(dāng)然也可以被持久化到一個(gè)數(shù)據(jù)庫(kù)。
下面是Address類:
@Entity
@Table(name = "t_address")
@XmlType(propOrder = ...{"street", "zipcode", "city", "country"})
@XmlAccessorType(XmlAccessType.FIELD)
public class Address ...{
@XmlTransient
@Id @GeneratedValue
private Long id;
private String street;
@Column(length = 100)
private String city;
@Column(name = "zip_code", length = 10)
@XmlElement(name = "zip")
private String zipcode;
@Column(length = 50)
private String country;
@XmlTransient
@ManyToMany(cascade = CascadeType.PERSIST)
@JoinTable(name = "t_address_tag",
joinColumns = ...{@JoinColumn(name = "address_fk")},
inverseJoinColumns = ...{@JoinColumn(name = "tag_fk")})
private List<Tag> tags = new ArrayList<Tag>();
// Constructors, getters, setters
}
( 完 )