JAXB 实现 XML & JAVABEAN 的转换
Posted 玄范
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JAXB 实现 XML & JAVABEAN 的转换相关的知识,希望对你有一定的参考价值。
一 前言
集成接口项目的开发,与第三方系统或中间平台等进行交互,支持服务端&客户端,支持 http/ https/ Webservice 等多种交互方式,数据传输主要采用XML(部分可能会在XML中以<![CDATA[......]]> 的形式包裹 JSON字符串),以后也可能会采用 JSON 格式。鉴于功能需要,必然要将XML传输的数据转换为JAVA对象来进行数据的处理,以及将JAVA对象转换为XML作为报文进行传输,因此,给出以下方案:利用JAXB技术来实现JAVA对象与XML的自由转换,以及对<![CDATA[......]]> 内容的处理。
根据业务与需求的复杂程度,本文中所列解决方案不能全部覆盖,因此会在今后进行补充。
二 JAXB概述
JAXB(Java Architecture for XML Binding) 是一个业界标准,是可根据XML Schema产生Java类的技术。同时,JAXB不仅提供了将XML反向生成Java对象树的方法,而且能将Java对象树重新写到
XML文档,实现Java对象与XML的互相转换。
Jaxb 2.0是JDK 1.6的组成部分,无需引入第三方jar包。Jaxb2.0使用了JDK的新特性,如:Annotation、泛型(GenericType)等
重要概念:
JAXBContext类,是应用的入口,用于管理XML/Java绑定信息。
Marshaller接口,将Java对象序列化为XML数据。
Unmarshaller接口,将XML数据反序列化为Java对象。
常用注解(annotation):
@XmlType,将Java类或枚举类型映射到XML Schema Type
@XmlAccessorType(XmlAccessType.FIELD) ,控制字段或属性的序列化。FIELD表示JAXB将自动绑定Java类中的每个非静态的(non-static)、非瞬态的(由@XmlTransient标注)字段到XML。其他值还有XmlAccessType.PROPERTY和XmlAccessType.NONE 等。
@XmlAccessorOrder,控制JAXB 绑定类中属性和字段的排序。
@XmlJavaTypeAdapter,使用定制的适配器(即扩展抽象类XmlAdapter并覆盖marshal()和unmarshal()方法),以序列化Java类为XML。
@XmlElementWrapper ,对于数组或集合(即包含多个元素的成员变量),生成一个包装该数组或集合的XML元素(称为包装器)。
@XmlRootElement,将Java类或枚举类型映射到XML元素。
@XmlElement,将Java类的一个属性映射到与属性同名的一个XML元素。
@XmlAttribute,将Java类的一个属性映射到与属性同名的一个XML属性。
@XmlTransient
[email protected]
@XmlType用在class类的注解,常与@XmlRootElement,@XmlAccessorType一起使用;
它有三个属性:name、propOrder、namespace,经常使用的只有前两个属性。如:
@XmlType(name = "basicStruct", propOrder = {"intValue","stringArray","stringValue")
在使用@XmlType的propOrder 属性时,必须列出JavaBean对象中的所有属性,否则会报错。
若同时使用了@XmlType(propOrder={})和 @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)的时候,生成的XML只会按照propOrder定义的顺序生成元素
[email protected]
@XmlRootElement用于类级别的注解,对应XML的根元素,常与 @XmlType 和 @XmlAccessorType一起使用;
若制定属性name,则会指定其在XML中的显示的名称,如:
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="ADDRESS_INFO")
public class Address {......}
[email protected]
@XmlElement将java对象的属性映射为XML的节点,在使用@XmlElement时,可通过name属性改变java对象属性在XML中显示的名称。如:
@XmlElement(name="ADDRESS")
private String yourAddress;
[email protected]
@XmlAttribute用于把java对象的属性映射为XML的属性,并可通过name属性为生成的xml属性指定别名。如:
@XmlAttribute(name="state_name")
private String state;
[email protected]
@XmlAccessorType用于指定由java对象生成xml文件时对java对象属性的访问方式。常与@XmlRootElement、@XmlType一起使用。它的属性值是XmlAccessType的4个枚举值,分别为:
- XmlAccessType.FIELD: java对象中的所有non-static/没有被@XmlTransient注解的成员变量
- XmlAccessType.PROPERTY:java对象中所有通过getter/setter方式访问的成员变量
- XmlAccessType.PUBLIC_MEMBER:java对象中所有的public访问权限的成员变量和通过getter/setter方式访问的成员变量
- XmlAccessType.NONE: java对象的所有属性都不映射为xml的元素
注意:@XmlAccessorType的默认访问级别是XmlAccessType.PUBLIC_MEMBER,因此,如果java对象中的private成员变量设置了public权限的getter/setter方法,就不要在private变量上使用@XmlElement和@XmlAttribute注解,否则在由java对象生成xml时会报同一个属性在java类里存在两次的错误。同理,如果@XmlAccessorType的访问权限为XmlAccessType.NONE,如果在java的成员变量上使用了@XmlElement或@XmlAttribute注解,这些成员变量依然可以映射到xml文件。
注意:虽然@XmlAccessorType为XmlAccessType.NONE,但是在java类的私有属性上加了@XmlAttribute和@XmlElement注解后,这些私有成员会映射生成xml的元素
[email protected]
@XmlAccessorOrder用于对java对象生成的xml元素进行排序。它有两个属性值:
AccessorOrder.ALPHABETICAL:对生成的xml元素按字母书序排序
XmlAccessOrder.UNDEFINED:不排序
[email protected]
@XmlTransient用于标识在由java对象转换为XML时,忽略此属性,即,在生成的XML文件中不会出现该节点。
[email protected]
@XmlJavaTypeAdapter常用在转换比较复杂的对象时,如map类型或者格式化日期等。使用此注解时,需要自己写一个adapter类继承XmlAdapter抽象类,并实现里面的方法。
@XmlJavaTypeAdapter(DateXmlAdapter.class)
DateXmlAdapter为自己定义的adapter类,来处理日期格式化问题
@XmlJavaTypeAdapter(DateXmlAdapter.class)
private Date createDate;
注解表示生成一个包装 XML 表示形式的包装器元素。 此元素主要用于生成一个包装集合的包装器 XML 元素。
注:@XmlElementWrapper仅允许出现在集合属性上
三 实例
针对前言中所讲,下面我们将展开一个实例,来处理和XML相关的转换.
根据上面Jaxb概述,我们在java对象上均使用annotation来注解,为了能够更加清晰直观的展示javabean中的显示,针对对象属性的访问方式我们统一为
<span style="white-space:pre"> </span>@XmlAccessorType(XmlAccessType.FIELD)
首先,我们先准备好我们的javabean:
Employee Address Description
Employee.java
package com.moonreal.java.jaxb.bean; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import com.moonreal.java.jaxb.xmladapter.CDATAAdapter; import com.moonreal.java.jaxb.xmladapter.DateXmlAdapter; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name="EMPLOYEE_AS_ROOT") //@XmlType(name = "employee", propOrder = { }) public class Employee implements Serializable{ /** * */ private static final long serialVersionUID = -2203839474229426435L; @XmlAttribute(name="emp_id", required = true) // @XmlElement private Integer id; @XmlElement(name = "Employee_name", required = true) private String name; @XmlElement(required = true) private String Gender=""; @XmlJavaTypeAdapter(DateXmlAdapter.class) private Date hiringDate; @XmlElementWrapper(name="ADDRESSES") @XmlElement(name="address") private List<Address> address = new ArrayList<Address>(); @XmlJavaTypeAdapter(CDATADescriptionAdapter.class) private Description desc; @XmlJavaTypeAdapter(CDATAStringAdapter.class) private String jsonBlock; // @XmlJavaTypeAdapter(MapXmlAdapter.class) // @XmlElement(name="addresses") // private Map<String,Address> addressMap; //getter/setter methods and overrided toString() method }
Address.java
package com.moonreal.java.jaxb.bean; import java.io.Serializable; import java.util.Date; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import com.moonreal.java.jaxb.xmladapter.DateXmlAdapter; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement public class Address implements Serializable{ /** * */ private static final long serialVersionUID = 1270713109637521319L; private String type; private String address; @XmlJavaTypeAdapter(DateXmlAdapter.class) private Date period; <pre code_snippet_id="1682303" snippet_file_name="blog_20160513_5_4039453" name="code" class="java">//getter/setter methods and overrided toString() method}
Description.java
package com.moonreal.java.jaxb.bean; import java.io.Serializable; import java.util.Date; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.moonreal.java.jaxb.jsonSerializer.DateJsonSerializer; import com.moonreal.java.jaxb.xmladapter.DateXmlAdapter; @XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name="description") public class Description implements Serializable{ /** * */ private static final long serialVersionUID = -1749942836341705166L; private String descName; private String descMsg; @XmlJavaTypeAdapter(DateXmlAdapter.class) @JsonSerialize(using = DateJsonSerializer.class) private Date createDate; //getter/setter methods and overrided toString() method}
处理XML与Java对象之间的转换:
package com.moonreal.java.jaxb; import java.io.IOException; import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import javax.xml.bind.JAXBContext; import javax.xml.bind.Marshaller; import javax.xml.bind.Unmarshaller; import com.moonreal.java.jaxb.constant.CommonConstant; import com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler; /** * @author xuan.fan * @create May 11, 2016 3:04:32 PM */ public class XMLUtil { /** * Convert from Object to XML <br/> * Default encoding UTF-8 * * @param obj * @return */ public static String convertToXml(Object obj) { return convertToXml(obj, CommonConstant.ENCODING_UTF8); } /** * Convert from Object to XML * * @param obj * @param encoding * @return */ public static String convertToXml(Object obj, String encoding) { String result = null; StringWriter writer = new StringWriter(); try { JAXBContext context = JAXBContext.newInstance(obj.getClass()); Marshaller marshaller = context.createMarshaller(); marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); marshaller.setProperty(Marshaller.JAXB_ENCODING, encoding); // marshaller.setProperty(Marshaller.JAXB_FRAGMENT, Boolean.FALSE); marshaller.setProperty("com.sun.xml.internal.bind.marshaller.CharacterEscapeHandler", new CharacterEscapeHandler() { public void escape(char[] chars, int start, int length, boolean isAttVal, Writer writer) throws IOException { writer.write(chars, start, length); } }); marshaller.marshal(obj, writer); // result = writer.toString(); // remove the standalone="yes" in the xml header result = writer.toString().replace("standalone=\"yes\"", ""); } catch (Exception e) { throw new RuntimeException(e); }finally{ if(writer!=null){ try { writer.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } return result; } /** * Convert from XML to Object * * @param xml * @param clazz * @return */ @SuppressWarnings("unchecked") public static <T> T convertToObject(String xml, Class<T> clazz) { T _clazz = null; StringReader reader = null; try { JAXBContext context = JAXBContext.newInstance(clazz); Unmarshaller unmarshaller = context.createUnmarshaller(); reader = new StringReader(xml); _clazz = (T) unmarshaller.unmarshal(reader); } catch (Exception e) { throw new RuntimeException(e); }finally{ if(reader!=null){ reader.close(); } } return _clazz; } }
处理JSON与Java对象之间的转换:
JsonUtils.java
package com.moonreal.java.jaxb; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; public class JsonUtils { private final static ObjectMapper mapper = new ObjectMapper(); public static <I extends Object> String toJsonWithPrettyPrinter(I input) { String result = null; if (input != null) { try { result = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(input); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } return result; } public static <I extends Object> String toJson(I input) { String result = null; if (input != null) { try { result = mapper.writeValueAsString(input); } catch (JsonProcessingException e) { throw new RuntimeException(e); } } return result; } public static <I extends Object> I fromJson(String str, Class<I> clazz) { I result = null; if (str!=null && !str.isEmpty()) { try { result = mapper.readValue(str, clazz); } catch (Exception e) { throw new RuntimeException(e); } } return result; } }
考虑到,在javabean转换为XML的时候,某些类型如MAP,Date等,生成的格式并非我们期望的,因此我们需要一些适配器(adapter)来进一步的处理,这里列出两种不同的adapter,非别针对Date类型和特殊的CDATA数据:
DateXmlAdapter.java
package com.moonreal.java.jaxb.adapter; import java.text.SimpleDateFormat; import java.util.Date; import javax.xml.bind.annotation.adapters.XmlAdapter; import com.moonreal.java.jaxb.constant.CommonConstant; /** * Adapter for converting from Date to XML using JAXB, vice versa. * * @author xuan.fan * @create May 11, 2016 2:59:34 PM */ public class DateXmlAdapter extends XmlAdapter<String, Date> { private SimpleDateFormat dateFormat = new SimpleDateFormat(CommonConstant.DATE_FORMAT_TO_SECOND); @Override public Date unmarshal(String date) throws Exception { return dateFormat.parse(date); } @Override public String marshal(Date date) throws Exception { return dateFormat.format(date); } }
CDATAAdapter.java
package com.moonreal.java.jaxb.xmladapter; import javax.xml.bind.annotation.adapters.XmlAdapter; import com.moonreal.java.jaxb.JsonUtils; import com.moonreal.java.jaxb.bean.Description; public class CDATADescriptionAdapter extends XmlAdapter<String, Description> { @Override public Description unmarshal(String v) throws Exception { Description o = JsonUtils.fromJson(v, Description.class); return o; } @Override public String marshal(Description v) throws Exception { String jsonStr = JsonUtils.toJson(v); return "<![CDATA[" + jsonStr + "]]>"; } }
针对包含在上面处理的CDATA部分的Json字符串进行转换时,涉及到Date类型的数据,对于日期格式的显示问题处理,同JAXB一样,jackjson也需要一个类似adapter的serializer来进行格式化,如下:
DateJsonSerializer.java
package com.moonreal.java.jaxb.jsonSerializer; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.moonreal.java.jaxb.constant.CommonConstant; /** * Format Date * * @author xuan.fan * @create May 13, 2016 11:48:40 AM */ public class DateJsonSerializer extends JsonSerializer<Date> { private SimpleDateFormat dateFormat = new SimpleDateFormat(CommonConstant.DATE_FORMAT_TO_SECOND); @Override public void serialize(Date date, JsonGenerator generator, SerializerProvider provider) { try { String formattedDate = dateFormat.format(date); generator.writeString(formattedDate); } catch (JsonProcessingException jpex) { throw new RuntimeException(jpex); } catch (IOException e) { throw new RuntimeException(e); } } }
根据前面的准备工作,现在我们做一个测试,看下我们的输入输出结果
package com.moonreal.java.jaxb; import java.util.ArrayList; import java.util.Date; import java.util.List; import com.moonreal.java.jaxb.bean.Address; import com.moonreal.java.jaxb.bean.AppendCaseHeader; import com.moonreal.java.jaxb.bean.Body; import com.moonreal.java.jaxb.bean.Description; import com.moonreal.java.jaxb.bean.Employee; import com.moonreal.java.jaxb.bean.Header; import com.moonreal.java.jaxb.bean.Packet; public class MainTest { public static void main(String[] args) { Employee emp = new Employee(); emp.setId(123); emp.setHiringDate(new Date()); emp.setName("xuan.fan"); Address a = new Address(); a.setAddress("sfdsf"); a.setPeriod(new Date()); Address a1 = new Address(); a1.setAddress("ssssss"); a1.setPeriod(new Date()); List<Address> list = new ArrayList<Address>(); list.add(a1); list.add(a); emp.setAddress(list); String xml = XMLUtil.convertToXml(emp); System.out.println(xml); System.out.println("\n============================="); Employee e = XMLUtil.convertToObject(xml, Employee.class); System.out.println(e); System.out.println("\n============================="); StringBuffer sb = new StringBuffer(); sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?> "); sb.append("<EMPLOYEE_AS_ROOT emp_id=\"123\"> "); sb.append(" <Employee_name>xuan.fan</Employee_name> "); sb.append(" <Gender></Gender> "); sb.append(" <hiringDate>2016-05-12 16:57:31</hiringDate> "); sb.append(" <hobits/> "); sb.append(" <desc><![CDATA[{\"descName\":\"Michal Jackson\",\"descMsg\":\"Cannot coming Wuxi\",\"createDate\":\"2016-05-13\"}]]></desc> "); sb.append("</EMPLOYEE_AS_ROOT> "); Employee em = (Employee) XMLUtil.convertToObject(sb.toString(), Employee.class); Description desc = em.getDesc(); if(desc!=null) System.out.println(desc.getDescMsg()); System.out.println(em); System.out.println("\n============================="); desc = new Description(); desc.setCreateDate(new Date()); em.setDesc(desc); String xml2 = XMLUtil.convertToXml(em); System.out.println(xml2); } }
执行程序,输出结果为:
<?xml version="1.0" encoding="UTF-8" ?> <EMPLOYEE_AS_ROOT emp_id="123"> <Employee_name>xuan.fan</Employee_name> <Gender></Gender> <hiringDate>2016-05-13 15:43:24</hiringDate> <ADDRESSES> <address> <address>ssssss</address> <period>2016-05-13 15:43:24</period> </address> <address> <address>sfdsf</address> <period>2016-05-13 15:43:24</period> </address> </ADDRESSES> </EMPLOYEE_AS_ROOT> ============================= Employee [id=123, name=xuan.fan, gender=, hiringDate=Fri May 13 15:43:24 CST 2016, addresses=[Address [type=null, address=ssssss, period=Fri May 13 15:43:24 CST 2016], Address [type=null, address=sfdsf, period=Fri May 13 15:43:24 CST 2016]], desc=null] ============================= Cannot coming Wuxi Employee [id=123, name=xuan.fan, gender=, hiringDate=Thu May 12 16:57:31 CST 2016, addresses=[], desc=Description [descName=Michal Jackson, descMsg=Cannot coming Wuxi, createDate=Fri May 13 08:00:00 CST 2016]] ============================= <?xml version="1.0" encoding="UTF-8" ?> <EMPLOYEE_AS_ROOT emp_id="123"> <Employee_name>xuan.fan</Employee_name> <Gender></Gender> <hiringDate>2016-05-12 16:57:31</hiringDate> <ADDRESSES/> <desc><![CDATA[{"descName":"Michal Jackson","descMsg":"Cannot coming Wuxi","createDate":"2016-05-13 08:00:00"}]]></desc> </EMPLOYEE_AS_ROOT>
四 开发
上面的实例如何,就不详细讲解了,有兴趣可以了解下。
我们在开发中,针对不同的业务场景,报文数据是不一样的,因此我们需要根据不同的场景创建不同的javabean来与之对应,以便进行相互转换
下面讲一下我们在开发中创建javabean需要遵循的原则:
1. 根据报文XML的结构,定义与之相匹配的 javabean,从根节点开始,所有子节点对应的类均要创建;
2. 所有对应的javabean均要实现接口 Serializable,例如:
public class Employee implements Serializable{.......}
3. 所有对应的javabean均在类上添加如下注解:
@XmlAccessorType(XmlAccessType.FIELD) @XmlRootElement(name="XXXXXXX") public class Employee implements Serializable{4. 所有对应的javabean中若存在Date类型的成员变量,则在该成员变量上添加如下注解:
<span style="white-space:pre"> </span>@XmlJavaTypeAdapter(DateXmlAdapter.class) private Date hiringDate;5. 若报文xml中存在 <![CDATA[ ...... ]]> 块,那么它对应的javabean中对应的成员变量则根据类型,必须添加如下注解:
a. 若成员变量为实例中的Description类型,那么javabean中对应的desc上要添加如下注解:
<span style="white-space:pre"> </span>@XmlJavaTypeAdapter(CDATADescriptionAdapter.class) private Description desc;b.若成员变量为实例中的 String类型,那么javabean中对应的jsonBlock上要添加如下注解:
<span style="white-space:pre"> </span>@XmlJavaTypeAdapter(CDATAStringAdapter.class) private String jsonBlock;c. ......
6. XML区分大小写,若成员变量与对应的xml节点大小写不一致,或名称不一致,则必须添加@XmlElement(name="xxxxxx")注解,来显式的指定在xml中显示的名称,例如:
<span style="white-space:pre"> </span>@XmlElement(name = "Employee_name") private String name;7. 所有的集合类型成员变量,均需要在变量添加@XmlElementWrapper(name="XXX")注解,来确定在生成xml的时候,被XXX节点包裹,例如:
<span style="white-space:pre"> </span>@XmlElementWrapper(name="ADDRESSES") @XmlElement(name="address") private List<Address> address = new ArrayList<Address>();
8. 若某个成员变量在转换时无需转换,则在变量上添加注解 @XmlTransient 来标识;
9. 若javabean在转xml时,某一个成员变量 value=null,则转换生成的xml中没有对应的节点,若需要显示,请给出默认值,例如:String str="";
10. 若对应的javabean中的某一个成员变量,在转换为xml后,并不是以“节点”的形式展示,而是该javabean对应的“节点”的属性,则必须添加如下注解:
<span style="white-space:pre"> </span>@XmlAttribute(name="emp_id") private Integer id;11. 若对应的javabean中存在Map类型的成员变量,则必须在对应的变量上添加如下adapter注解(以后根据需要增加对Map类型的adapter):
<span style="white-space:pre"> </span>@XmlJavaTypeAdapter(MapXmlAdapter.class) <span style="white-space:pre"> </span>@XmlElement(name="addresses") <span style="white-space:pre"> </span>private Map<String,Address> addressMap;
上面是目前能够想到的需要遵循的原则,以后会根据需要进行扩展或缩减。
调用点:
1. 在报文请求过来,我们拿到报文xml之后,根据配置我们找到对应的javabean,以Employee为例
<span style="white-space:pre"> </span>String xml = ...... Employee em = XMLUtil.<strong>convertToObject</strong>(xml, Employee.class);2. 在发送请求报文时,根据我们的javabean生成相应的xml报文,以Employee为例
<span style="white-space:pre"> </span>Employee emp = ......; String xml = XMLUtil.convertToXml(emp);
这样,我们就得到了相应的对象和xml,接下来就可以根据得到的对象进行相关校验或者存储或者业务的处理,同时,也可以将的到的xml发送出去等。
五 总结
以上内容主要是提供一个jaxb进行javabean与xml互相转换的实例,通过该实例,能够大概了解jaxb的工作原理,以便为之后的工作提供便利。在“四 开发”章节,主要是对实际开发中统一编程风格,减少不必要的问题进行的开发原则约束。
若已经对jaxb有所了解,可以直接跳过,直接阅读第四章节。
以上是关于JAXB 实现 XML & JAVABEAN 的转换的主要内容,如果未能解决你的问题,请参考以下文章