使用JAXB从一个对象模型中产生XML文档

来源:百度文库 编辑:神马文学网 时间:2024/05/02 02:11:54
使用JAXB从一个对象模型中产生XML文档   [ http://tech.it168.com/j/2007-04-01/200704011813902.shtml ]
作者: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 objectsTag tag1 = new Tag("working hours");Tag tag2 = new Tag("week-ends");Tag tag3 = new Tag("mind the dog");// Instantiates an individual object with home addresscalendar.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 addressAddress deliveryAddress1 = new Address(3L, "Findsbury Avenue", "London", "CE451", "UK");deliveryAddress1.addTag(tag1);deliveryAddress1.addTag(tag3);individual.addDeliveryAddress(deliveryAddress1);// Instantiates a second delivery addressAddress deliveryAddress2 = new Address(4L, "Camden Street", "Brighton", "NW487", "UK");deliveryAddress2.addTag(tag1);deliveryAddress2.addTag(tag2);individual.addDeliveryAddress(deliveryAddress2);// Generates XML representation of an individualStringWriter 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 IndividualLondonUK3Findsbury Avenueworking hoursmind the dogCE451BrightonUK4Camden Streetworking hoursweek-endsNW487ringo@star.co.ukLondonUK2Abbey RoadSW141+187445
通过一个注释@XmlRootElemen,一个Marshaller对象和异常产生的代码,可以很容易的得到对象图形的XML表示。根元素代表Customer对象,它包括所有的属性(一个家庭地址,两个发货地址,一个ID,一个电话号码等)。
定制XML文档
音乐公司和他的商业伙伴对上面给出的XML文档(Listing 2)并不完全满意,他们可能抛弃某些信息(地址标识符号,tags)出生日期的格式,订单的某些属性等。由于有了javax.xml.bind.annotation包的注释,JAXB提供了一种方式来定制和控制XML的结构。
首先,如果你想抛弃元素而使用或者来替代根元素。如果让JAXB不使用抽象Customer类,可以放弃使用@XmlRootElement而使用@XmlTransient来产生临时类。
XML文档是由一系列的元素(value)和属性()组成。JAXB使用两种注释来区分他们:@XmlAttribute 和 @XmlElement,每个注释有一系列的参数可以对属性进行重命名,可以为空值,给定的一个默认值等。下列代码使用两种注释来将id转换为XMl的属性(而不是元素)并且重命名了发货地址元素(将address改为deliveryAddress):@XmlTransientpublic abstract class Customer ...{@XmlAttributeprotected Long id;protected String telephone;protected String email;protected Address homeAddress;@XmlElementWrapper(name = "delivery")@XmlElement(name = "address")protected List
deliveryAddresses = new ArrayList
();// Constructors, getters, setters}
这段代码使用了@XmlElementWrapper注释,它产生包装元素在发货地址的外围。再看Listing 2,有个元素,通过上面的代码,就可以在
元素前加了元素。
继续讨论地址,如果想要放弃标识符和tags,可以使用@XmlTransient注释。为了重命名一个元素,使用@XmlElement注释的name属性。下列代码就对属性zipcode重命名为元素:@XmlType(propOrder = ...{"street", "zipcode", "city", "country"})@XmlAccessorType(XmlAccessType.FIELD)public class Address ...{@XmlTransientprivate Long id;private String street;private String city;@XmlElement(name = "zip")private String zipcode;private String country;@XmlTransientprivate List tags = new ArrayList();// Constructors, getters, setters}
上面的@XmlType注释可以将一个类或者枚举映射为一个XML schema类型。可以使用它来指定一个命名空间或者使用propOrder属性来定制属性,按照这个定制可以列出属性的名字和产生XML文档。
Table 1显示了XML文档的三个不同的摘录:
Default XML Representation
Annotated Customer Class
Annotated Address Class


London
UK
3
Findsbury

working hours


mind the dog

CE451


Brighton
UK
4
Camden

working hours


week-ends

NW487

(...)




London
UK
3
Findsbury

working hours


mind the dog

CE451


Brighton
UK
4
Camden

working hours


week-ends

NW487


(...)




Findsbury
CE451
London
UK


Camden
NW487
Brighton
UK


(...)

也可以注释具体的类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属性为默认值,例如,个体的出生日期将显示为下列格式:1940-08-07T00:00:00.781+02:00
为了格式化日期(如: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 ...{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


Ringo
07/08/1940
+187445
ringo@star.co.uk

Abbey Road
SW14
London
UK



Findsbury Avenue
CE451
London
UK


Camden Street
NW487
Brighton
UK



Company XML Document


Mr Father
+14519454
contact@sony.com
25000

General Alley
75011
Paris
FR



St James St
SW14
London
UK


Central Side Park
7845
New York
US



Unmarshal和产生Schema
如图1所示,JAXB可以用来unmarshal,产生和编译一个schema,也就是用先前获得的XML文档来产生对象图表。首先得到一个JAXBContext,创建一个Unmarshaller对象,调用unmarshal方法,然后返回Individual的属性及他的一个实例:// xmlString contains the XML document of an individualStringReader 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());
一个XML schema描述了XML文档的结构,用XML语法来写的。如果你对XML schema了解的不多,你也可以使用Sun的JAXB实现提供的schemaGen工具来产生一个XML schema。如Listing 3,可以看到Address, Company, Individual和 Tag类被描述为复杂的类型:maxOccurs="unbounded"/>minOccurs="0" maxOccurs="unbounded"/>
如果下载JAXB,schema编译器(xjc)也会被一同下载。
持久化注释
如果你认为JAXB可以将数据持久化到XML。JPA则是和它差不多的关系数据库的术语,事实上,二者都是依靠的注释,这就意味着同样的类都可以被JPA和JAXB注释,按照这种说法,可以提供一个XML表示也当然也可以被持久化到一个数据库。
下面是Address类:@Entity@Table(name = "t_address")@XmlType(propOrder = ...{"street", "zipcode", "city", "country"})@XmlAccessorType(XmlAccessType.FIELD)public class Address ...{@XmlTransient@Id @GeneratedValueprivate 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 tags = new ArrayList();// Constructors, getters, setters}
( 完 )