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。