| |
引入 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 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): @xmltransientpublic 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> |
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类被描述为复杂的类型: <?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)也会被一同下载。
持久化注释 如果你认为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}
|
|