技术开发 频道

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


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
// 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。 

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):
@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>

 

 

 

 




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")
@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>

 

 

 

 



IT168技术文档】 
     Unmarshal和产生Schema 
     如图1所示,JAXB可以用来unmarshal,产生和编译一个schema,也就是用先前获得的XML文档来产生对象图表。首先得到一个JAXBContext,创建一个Unmarshaller对象,调用unmarshal方法,然后返回Individual的属性及他的一个实例:
// 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());
     一个XML schema描述了XML文档的结构,用XML语法来写的。如果你对XML schema了解的不多,你也可以使用Sun的JAXB实现提供的schemaGen工具来产生一个XML schema。如Listing 3,可以看到Address, Company, Individual和 Tag类被描述为复杂的类型:
<?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)也会被一同下载。

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 }

0
相关文章