作者:IT168 gmplayer 2007-04-01
引入 Java提供各种方式来处理XML,其中包括: 使用简单的文件I/O或者javax.xml.stream.XmlStreamWriter. 使用XML序列化java.beans.XMLEncoder,它能够产生一个Java Bean的XML表示法,同样,ObjectOutputStream也能够用来创建序列化对象的二进制表示法。 使用专门的类库像XStream,直接使用SAX(XML的简单API)或者通过JAXP API来使用DOM(文档对象模型)。 尽管XML和Java技术已经在数据交换上已经有成熟的模型,但是将一个Java对象模型映射到XML和将XML映射为Java对象模型还是有点神秘的。可以考虑使用JAXB作为一种解决方案,JAXB (Java Architecture for XML Binding)可以使你将XML转换为Java数据绑定和从XML schemas产生Java类,反之也是可以的。它非常方便且容易使用,它提供了像XML验证和使用注释和适配器进行定制。下图阐述了JAXB的用法:

JAXB API在javax.xml.bind包中被定义,它是一系列的接口和类,从schema产生的代码可以使应用程序进行通讯。JAXB API最主要的就是javax.xml.bind.JAXBContext类,JAXBContext是一个抽象的类,它可以管理XML/Java 绑定,也可以被看作为一个工厂,因为它提供: Unmarshaller类可以将XML转换为Java变得连续并且可以随意的验证XML(使用setSchema方法) Marshaller类使一个对象图形到XML的转换变得连续并且可以随意的验证。 首先,JAXB通过使用schema generator能够在一个XML schema中定义一系列的类,它也提供相反的操作,允许你通过schema compiler从一个给定的XML schema产生Java类的集合。 schema compile将XML schema看作为输入并产生一个Java类和接口的包,这个接口反应了在源schema中定义的规则。这些类是被注释使用一个可定制的Java-XML映射提供运行时框架。 JAXB也可以使用schema generator从一个XML schema中产生一个Java对象层或者提供一个对象Java层来描述相应的XML schema。运行时框架提供了相应的unmarshalling, marshalling和验证功能。也就是说,你可以从一个XML文档转换为一个对象图形(unmarshalling)或者将一个对象图形转换为XML格式(marshalling)。 这些功能就是为什么JAXB经常和Web service相关联的原因。Web service使用API来将对象转换为消息,该消息可以通过SOAP来进行发送。本文所使用的例子就是一个虚拟音乐公司的地址薄的应用。
产生XML 音乐公司销售它的音乐产品像乐器,唱片等,在它的地址薄中存储着两种类型的客户:个体和公司。每一个客户都有一个家庭地址和一系列的发货地址。发货地址可以是周末或早上有效,这些信息可以以标签的形式添加到地址薄中。其形式如下图所示:

该公司想要以XML形式发送一些客户的信息给合作伙伴,因此它需要一个给定客户的对象模型的XML文档。使用JAXB实现起来很容易。下列代码创建了一个个体的实例并设置了他的属性(first name ,last name)一个家庭地址,两个发货地址。对象都设置好以后使用javax.xml.bind.Marshaller来产生个体对象的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);

这段代码使用静态方法newInstance来产生JAXBContext的一个实例。创建Marshaller对象,然后调用marshal方法产生一个个体对象的XML表示,即StringWinter。
接下来要做的就是增加@XmlRootElement注释到Customer类中,@XmlRootElement注释通知JAXB被注释的类是XML文档的根元素。如果该注释丢失,JAXB将抛出异常。如果增加了注释并运行程序将会得到下列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>
通过一个注释@XmlRootElemen,一个Marshaller对象和异常产生的代码,可以很容易的得到对象图形的XML表示。根元素<customer>代表Customer对象,它包括所有的属性(一个家庭地址,两个发货地址,一个ID,一个电话号码等)。
定制XML文档
音乐公司和他的商业伙伴对上面给出的XML文档(Listing 2)并不完全满意,他们可能抛弃某些信息(地址标识符号,tags)出生日期的格式,订单的某些属性等。由于有了javax.xml.bind.annotation包的注释,JAXB提供了一种方式来定制和控制XML的结构。
首先,如果你想抛弃<customer>元素而使用<individual>或者<company>来替代根元素。如果让JAXB不使用抽象Customer类,可以放弃使用@XmlRootElement而使用@XmlTransient来产生临时类。
XML文档是由一系列的元素(<element>value</element>)和属性(<element attribute="value"/>)组成。JAXB使用两种注释来区分他们:@XmlAttribute 和 @XmlElement,每个注释有一系列的参数可以对属性进行重命名,可以为空值,给定的一个默认值等。下列代码使用两种注释来将id转换为XMl的属性(而不是元素)并且重命名了发货地址元素(将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注释,它产生包装元素在发货地址的外围。再看Listing 2,有个<deliveryAddresses>元素,通过上面的代码,就可以在<address>元素前加了<delivery>元素。 继续讨论地址,如果想要放弃标识符和tags,可以使用@XmlTransient注释。为了重命名一个元素,使用@XmlElement注释的name属性。下列代码就对属性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注释可以将一个类或者枚举映射为一个XML schema类型。可以使用它来指定一个命名空间或者使用propOrder属性来定制属性,按照这个定制可以列出属性的名字和产生XML文档。 Table 1显示了XML文档的三个不同的摘录:
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来定制映射。首先作为XML文档的根元素,不得不使用@XmlRootElement注释来指定XML命名空间 http://www.watermelon.example/customer。该例子中使用@XmlType.propOrder来定制属性。可以使用从超类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属性为默认值,例如,个体的出生日期将显示为下列格式:<dateOfBirth>1940-08-07T00:00:00.781+02:00</dateOfBirth> 为了格式化日期(如:07/08/1953),有两种选择: 1. 使用日期类型javax.xml.datatype.XMLGregorianCalendar而不使用java.util.Date。 2. 使用一个适配器,就像上面代码看到的那样,个体类Individual使用@XmlJavaTypeAdapter注释。@XmlJavaTypeAdapter(DateAdapter.class)通知JAXB使用适配器调用DateAdapter当marshalling/unmarshalling属性dateOfBirth时。 写一个类(DateAdapter)来继承XmlAdapter。覆盖marshal 和 unmarshal方法。这种方法可以将日期按照一定格式的字符串进行格式化,反之亦然。下列代码使用java.text.SimpleDateFormat来格式化日期:
 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);
}
}
现在返回Listing 1,如果Marshaller.marshal()方法被调用,DateAdapter.marshal()也被调用,出生日期也被格式化了.下面是获得的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>
|
( 完 )
|