【IT168技术文档】
引入
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来进行发送。本文所使用的例子就是一个虚拟音乐公司的地址薄的应用。
【IT168技术文档】
产生XML
音乐公司销售它的音乐产品像乐器,唱片等,在它的地址薄中存储着两种类型的客户:个体和公司。每一个客户都有一个家庭地址和一系列的发货地址。发货地址可以是周末或早上有效,这些信息可以以标签的形式添加到地址薄中。其形式如下图所示:
该公司想要以XML形式发送一些客户的信息给合作伙伴,因此它需要一个给定客户的对象模型的XML文档。使用JAXB实现起来很容易。下列代码创建了一个个体的实例并设置了他的属性(first name ,last name)一个家庭地址,两个发货地址。对象都设置好以后使用javax.xml.bind.Marshaller来产生个体对象的XML表示。
Listing 1: Creates an XML Representation of an Individual
这段代码使用静态方法newInstance来产生JAXBContext的一个实例。创建Marshaller对象,然后调用marshal方法产生一个个体对象的XML表示,即StringWinter。// 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);
【IT168技术文档】
接下来要做的就是增加@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来产生临时类。
【IT168技术文档】
XML文档是由一系列的元素(<element>value</element>)和属性(<element attribute="value"/>)组成。JAXB使用两种注释来区分他们:@XmlAttribute 和 @XmlElement,每个注释有一系列的参数可以对属性进行重命名,可以为空值,给定的一个默认值等。下列代码使用两种注释来将id转换为XMl的属性(而不是元素)并且重命名了发货地址元素(将address改为deliveryAddress):
这段代码使用了@XmlElementWrapper注释,它产生包装元素在发货地址的外围。再看Listing 2,有个<deliveryAddresses>元素,通过上面的代码,就可以在<address>元素前加了<delivery>元素。@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 }
继续讨论地址,如果想要放弃标识符和tags,可以使用@XmlTransient注释。为了重命名一个元素,使用@XmlElement注释的name属性。下列代码就对属性zipcode重命名为<zip>元素:
上面的@XmlType注释可以将一个类或者枚举映射为一个XML schema类型。可以使用它来指定一个命名空间或者使用propOrder属性来定制属性,按照这个定制可以列出属性的名字和产生XML文档。@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 }
Table 1显示了XML文档的三个不同的摘录:
Default XML Representation
|
Annotated Customer Class
|
Annotated Address Class
|
<customer>
|
<individual id="1">
|
<individual id="1">
|
【IT168技术文档】
也可以注释具体的类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")
|
@XmlRootElement(name = "individual", namespace =
|
为了格式化日期(如: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来格式化日期:
现在返回Listing 1,如果Marshaller.marshal()方法被调用,DateAdapter.marshal()也被调用,出生日期也被格式化了.下面是获得的XML文档: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); } }
Individual XML Document
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
Company XML Document
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
【IT168技术文档】
Unmarshal和产生Schema
如图1所示,JAXB可以用来unmarshal,产生和编译一个schema,也就是用先前获得的XML文档来产生对象图表。首先得到一个JAXBContext,创建一个Unmarshaller对象,调用unmarshal方法,然后返回Individual的属性及他的一个实例:
一个XML schema描述了XML文档的结构,用XML语法来写的。如果你对XML schema了解的不多,你也可以使用Sun的JAXB实现提供的schemaGen工具来产生一个XML schema。如Listing 3,可以看到Address, Company, Individual和 Tag类被描述为复杂的类型:// 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());
如果下载JAXB,schema编译器(xjc)也会被一同下载。<?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>
【IT168技术文档】
持久化注释
如果你认为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 @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 }