先附上XStream官方文档和API地址:
官方文档:http://x-stream.github.io/annotations-tutorial.html
API文档:http://x-stream.github.io/javadoc/
我在项目中使用了XStream来完成XML报文与Java Bean的转换操作,Bean序列化没有什么坑,但在反序列化时,遇到了一个较大的坑,踩了挺久。
因为驼峰命名规范的字段名与接口中规定报文字段名有所出入,接口中规定的格式有下划线,所以需要进行别名处理,但序列化时不好使!
最后从结果看来是有一个坑,加上自己有些理所当然。。
现在总结一下:
下面只谈及反序列化过程。。。。也就是XML转Java Bean
为了简化开发,在使用XStream时,使用了注解,所以为了注解生效需要开启注解,这里注意反序列化必须通过调用processAnnotations()来开启注解,不能通过autodetectAnnotations()开启 !!!
先放上XML和Bean
需要被解析的XML报文
<?xml version="1.0" encoding="utf-8"?>
<business_trans version="1.0">
<response_type>barcode_pay_response</response_type>
<isspid>000000</isspid>
<pos_id>0</pos_id>
<pos_seq>000000</pos_seq>
<result>
<id>7000</id>
<comment>没有该终端</comment>
</result>
</business_trans>
需要XML反序列化成的Bean
原因分析:
通过autodetectAnnotations()这种懒加载方式开启, XStream实例缓存为注解处理的所有类类型。每次XStream转换一个对象时,它将在自动检测模式下首先处理对象的类型和所有相关的类型。因此,将对象序列化为XML是没有问题的,因为XStream已经加载了该类,扫描了注解,预先知道了其中的所有类型。
在反序列化时不再是这样。XStream必须知道别名才能将其转换为适当的类型,但是只有在预先处理了该类型之后,它才能找到别名的注释。因此,如果类型还没有反序列化,则反序列化将失败。
参考代码:
static {
//指定解析编码为GBK,并解决XStream对出现双下划线的bug
xStreamForReqData = new XStream(new DomDriver("GBK", new XmlFriendlyNameCoder("-_",
"_")));
//开启注解(在类加载时调用一次,在后续每次调用序列化方法时会开启当前类的注解)
xStreamForReqData.autodetectAnnotations(true);
}
public static String objToXML(Object xmlData) throws Exception{
String postDataXML;
try {
//将要提交给API的数据对象转换成XML格式数据Post给API
String postDataXMLOld = xStreamForReqData.toXML(xmlData);
postDataXML = postDataXMLOld.replace(""", "\"");
//为Bean转换后的XML加上头信息
postDataXML = XML_TAG + postDataXML;
} catch (Exception e) {
logger.error("Object converts into XML go wrong", e);
throw e;
}
return postDataXML;
}
从上面这段代码分析,即从序列化角度来看,先调用方法开启,但实质在调用序列化方法序列化该Bean时,扫描该Bean注解,别名注解此时生效,作用相当于将Bean的字段直接重命名,然后做为XML标签名或者属性名。因为在选择序列化该Bean时,就已经能加载到该Bean,所以不会有问题。
参考代码:
public static Object xmlToObj(String xmlString) throws Exception{
Object obj;
try {
//指定解析编码为GBK
XStream xStreamForResData = new XStream(new DomDriver("GBK"));
xStreamForResData.autodetectAnnotations(true);
obj = xStreamForResData.fromXML(xmlString);
} catch (Exception e) {
logger.error("XML converts into Object go wrong", e);
throw e;
}
return obj;
}
反序列化时所报的错
但在反序列化时,这种懒加载导致还先未进行别名处理,就直接调用反序列化方法,它直接去根据根标签去加载类名为“business_trans”的类,加载到该类后,因为懒加载,这是才开始扫描XStream相关注解,但显然你的别名注解@XStreamAlias("business_trans")生效在后,加载类名为“business_trans”的类在前,此时你的Bean名称还未别名过,还为“YimaScanResData”,所以肯定找不到该类,所以反序列化肯定报错。
当然,如果在反序列化之前就已经扫描了该类的注解,那么你再反序列化就不会报错,因为在反序列化之前注解已经生效。比如通过这种延迟加载开启注解的方式,你先进行了该Bean的序列化,在进行XML反序列化该Bean,就不存在问题。如下图所示:
但还是不建议在反序列化时,使用这种注解开启方式。。
反序列化正确姿势:
public static Object xmlToObj(String xmlString, Class clazz) throws Exception{
Object obj;
try {
//指定解析编码为GBK
XStream xStreamForResData = new XStream(new DomDriver("GBK"));
xStreamForResData.processAnnotations(clazz);
obj = xStreamForResData.fromXML(xmlString);
} catch (Exception e) {
logger.error("XML converts into Object go wrong", e);
throw e;
}
return obj;
}
对于fromXML(),他有多种重载形式,这里我只用过两种一种如上,fromXML(java.lang.String xml),因为当前XStream实例只扫描了当前这一个class,所以找别名为“business_trans”,直接就能对应到这个类,然后反序列化。另一种如下,是提供一个所要反序列化的对象实例,然后XStream将该XML填充至该实例,同样别名注解预加载生效了,所以字段名是可以一一对应解析正确的!
public static Object xmlToObj(String xmlString, Object obj) throws Exception{
try {
//指定解析编码为GBK
XStream xStreamForResData = new XStream(new DomDriver("GBK"));
xStreamForResData.processAnnotations(obj.getClass());
obj = xStreamForResData.fromXML(xmlString, obj);
} catch (Exception e) {
logger.error("XML converts into Object go wrong", e);
throw e;
}
return obj;
}
反序列化后的实体Bean