本文出处:http://blog.csdn.net/chaijunkun/article/details/30257015,转载请注明。由于本人不定期会整理相关博文,会对相应内容作出完善。因此强烈建议在原始出处查看此文。
Java作为面向对象的语言,处理结构化的数据当然也可以将其对象化,这就是涉及到了转化工具。而对于XML文件来说,经常使用的是JDK 1.6开始支持的JAXB 2.0,另外还有一款叫做XStream的框架。本人在使用XStream遇到了一些问题,在此分享。
1.问题描述
在做项目的时候,为了提高性能,降低新建对象的频率,我开始时将XStream写成了单例模式。每当需要将对象序列化为xml,或者将xml反序列化为对象的时候,获取到这个XStream实例,然后调用转换API,但是在连续反序列化两个结构不同的XML的时候发现了抛出异常的bug。
2.基础代码
首先我写了一个测试用例,以下是XStream的单例模式和序列化对象和反序列化xml的代码实现:
public class TestCase {
private static volatile XStream stream= null;
public static XStream getStream(){
synchronized (TestCase.class) {
if (stream==null){
stream= new XStream();
}
return stream;
}
}
public static <T> void toXML(Object obj, Class<T> clazz, OutputStream out){
XStream stream= TestCase.getStream();
stream.processAnnotations(clazz);
stream.toXML(obj, out);
return;
}
public static <T> T fromXML(InputStream in, Class<T> clazz){
XStream stream= TestCase.getStream();
stream.processAnnotations(clazz);
Object obj= stream.fromXML(in);
try{
return clazz.cast(obj);
}catch(ClassCastException e){
return null;
}
}
}
然后我构建了两个根节点相同,但子节点有差异的XML文件结构:
结构1:
<root date="2014-06-12 09:28:34.614 UTC">
<demo>
<element>test1</element>
</demo>
<demo>
<element>test2</element>
</demo>
</root>
结构2:
<root date="2014-06-12 09:28:34.745 UTC">
<example>
<element>test1</element>
</example>
<example>
<element>test2</element>
</example>
</root>
接下来对两个结构分别做了映射:
结构1:
package net.csdn.blog.chaijunkun.xml.case1;
import java.util.Date;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias("root")
public class Case1 {
@XStreamAlias("date")
@XStreamAsAttribute
private Date date;
@XStreamImplicit
private List<Demo> demos;
public Date getDate() {
return date;
}
//一些getters and setters...
}
package net.csdn.blog.chaijunkun.xml.case1;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("demo")
public class Demo {
@XStreamAlias("element")
private String element;
//一些getters and setters...
}
结构2:
package net.csdn.blog.chaijunkun.xml.case2;
import java.util.Date;
import java.util.List;
import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
@XStreamAlias("root")
public class Case2 {
@XStreamAlias("date")
@XStreamAsAttribute
private Date date;
@XStreamImplicit
private List<Example> examples;
//一些getters and setters...
}
package net.csdn.blog.chaijunkun.xml.case2;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("example")
public class Example {
@XStreamAlias("element")
private String element;
//一些getters and setters...
}
3.测试用例
为了进行后续的反序列化,我们先用对象序列化成xml文件,然后将文件再反序列化过来,看能否成功。
结构1的序列化:
public static void demo1() throws FileNotFoundException{
Demo d1= new Demo();
d1.setElement("test1");
Demo d2= new Demo();
d2.setElement("test2");
List<Demo> demos= new LinkedList<Demo>();
demos.add(d1);
demos.add(d2);
Case1 c1= new Case1();
c1.setDemos(demos);
c1.setDate(new Date());
FileOutputStream out1= new FileOutputStream(new File("d:\\test1.xml"));
try{
TestCase.toXML(c1, Case1.class, out1);
}finally{
try {
out1.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结构2的序列化:
public static void demo2() throws FileNotFoundException{
Example e1= new Example();
e1.setElement("test1");
Example e2= new Example();
e2.setElement("test2");
List<Example> examples= new LinkedList<Example>();
examples.add(e1);
examples.add(e2);
Case2 c2= new Case2();
c2.setExamples(examples);
c2.setDate(new Date());
FileOutputStream out2= new FileOutputStream(new File("d:\\test2.xml"));
try{
TestCase.toXML(c2, Case2.class, out2);
}finally{
try {
out2.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
分别将两个结果保存为test1.xml和test2..xml之后,接下来尝试将它们反序列化过来:
public static void demo3() throws FileNotFoundException{
File f1= new File("d:\\test1.xml");
FileInputStream fis1= new FileInputStream(f1);
Case1 c11= TestCase.fromXML(fis1, Case1.class);
File f2= new File("d:\\test2.xml");
FileInputStream fis2= new FileInputStream(f2);
Case2 c22= TestCase.fromXML(fis2, Case2.class);
}
接下来执行JUnit测试用例:
@Test
public void doTest() throws FileNotFoundException{
demo1();
demo2();
demo3();
}
发现抛出如下异常:
com.thoughtworks.xstream.converters.ConversionException: Element demo of type net.csdn.blog.chaijunkun.xml.case1.Demo is not defined as field in type net.csdn.blog.chaijunkun.xml.case2.Case2
---- Debugging information ----
class : net.csdn.blog.chaijunkun.xml.case2.Case2
required-type : net.csdn.blog.chaijunkun.xml.case2.Case2
converter-type : com.thoughtworks.xstream.converters.reflection.ReflectionConverter
path : /root/demo
line number : 4
version : null
-------------------------------
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.writeValueToImplicitCollection(AbstractReflectionConverter.java:403)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.doUnmarshal(AbstractReflectionConverter.java:334)
at com.thoughtworks.xstream.converters.reflection.AbstractReflectionConverter.unmarshal(AbstractReflectionConverter.java:234)
at com.thoughtworks.xstream.core.TreeUnmarshaller.convert(TreeUnmarshaller.java:72)
...........
在错误信息中,path指示要分析的二级节点是demo,而之前规定的结构2的二级节点是example,事实上test2.xml的二级节点也是example。这说明序列化输出没有问题。问题出在了反序列化上。根据错误信息,XStream只存储了第一次反序列化配置的结构,而第二次调用时并没有更新相关注解配置(即使调用了processAnnotations注解)。
4.解决方法
针对单例模式的失败,退而求其次,只能将其改为工厂模式:
public static XStream getStream(){
return new XStream();
}
此时,两次反序列化都已正常。
由于一些扩展功能,自己的代码中实现了一个特殊的XStream Driver,注入这个Driver需要在XStream的构造过程中进行,因此将代码统一写进了getStream()方法中,以便每次得到的都是自定义的XStream(自定义Driver与本bug无关,去掉自定义Driver后bug依然可重现)。
5.写在后面
在静态方法toXML和fromXML中,我都加入了一个强制处理注解的方法调用processAnnotations。在官方API中,XStream实例有一个方法autodetectAnnotations可以设置自动处理注解。但是当反序列化时会出现更严重的类型无法转化问题。因此不建议使用该方法,而是强制使用processAnnotations。