标签:
一 前言
集成接口项目的开发,与第三方系统或中间平台等进行交互,支持服务端&客户端,支持 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
1.@XmlType
@XmlType用在class类的注解,常与@XmlRootElement,@XmlAccessorType一起使用;
它有三个属性:name、propOrder、namespace,经常使用的只有前两个属性。如:
@XmlType(name = "basicStruct", propOrder = {"intValue","stringArray","stringValue")
在使用@XmlType的propOrder 属性时,必须列出JavaBean对象中的所有属性,否则会报错。
若同时使用了@XmlType(propOrder={})和 @XmlAccessorOrder(XmlAccessOrder.ALPHABETICAL)的时候,生成的XML只会按照propOrder定义的顺序生成元素
2.@XmlRootElement
@XmlRootElement用于类级别的注解,对应XML的根元素,常与 @XmlType 和 @XmlAccessorType一起使用;
若制定属性name,则会指定其在XML中的显示的名称,如:
@XmlType
@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name="ADDRESS_INFO")
public class Address {......}
3.@XmlElement
@XmlElement将java对象的属性映射为XML的节点,在使用@XmlElement时,可通过name属性改变java对象属性在XML中显示的名称。如:
@XmlElement(name="ADDRESS")
private String yourAddress;
4.@XmlAttribute
@XmlAttribute用于把java对象的属性映射为XML的属性,并可通过name属性为生成的xml属性指定别名。如:
@XmlAttribute(name="state_name")
private String state;
5.@XmlAccessorType
@XmlAccessorType用于指定由java对象生成xml文件时对java对象属性的访问方式。常与@XmlRootElement、@XmlType一起使用。它的属性值是XmlAccessType的4个枚举值,分别为:
注意:@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的元素
6.@XmlAccessorOrder
@XmlAccessorOrder用于对java对象生成的xml元素进行排序。它有两个属性值:
AccessorOrder.ALPHABETICAL:对生成的xml元素按字母书序排序
XmlAccessOrder.UNDEFINED:不排序
7.@XmlTransient
@XmlTransient用于标识在由java对象转换为XML时,忽略此属性,即,在生成的XML文件中不会出现该节点。
8.@XmlJavaTypeAdapter
@XmlJavaTypeAdapter常用在转换比较复杂的对象时,如map类型或者格式化日期等。使用此注解时,需要自己写一个adapter类继承XmlAdapter抽象类,并实现里面的方法。
@XmlJavaTypeAdapter(DateXmlAdapter.class)
DateXmlAdapter为自己定义的adapter类,来处理日期格式化问题
@XmlJavaTypeAdapter(DateXmlAdapter.class)
private Date createDate;
9.@XmlElementWrapper
注解表示生成一个包装 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中对应的成员变量则根据类型,必须添加如下注解:
<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);
五 总结
以上内容主要是提供一个jaxb进行javabean与xml互相转换的实例,通过该实例,能够大概了解jaxb的工作原理,以便为之后的工作提供便利。在“四 开发”章节,主要是对实际开发中统一编程风格,减少不必要的问题进行的开发原则约束。
若已经对jaxb有所了解,可以直接跳过,直接阅读第四章节。
标签:
原文地址:http://blog.csdn.net/momoyiye/article/details/51388345