`

使用XStream序列化、反序列化XML数据时遇到的各种问题

 
阅读更多

现在参与的项目是一个纯Application Server,整个Server都是自己搭建的,使用JMS消息实现客户端和服务器的交互,交互的数据格式采用XML。说来惭愧,开始为了赶进度,所有XML消息都是使用字符串拼接的,而XML的解析则是使用DOM方式查找的。我很早就看这些代码不爽了,可惜一直没有时间去重构,最近项目加了几个人,而且美国那边也开始渐渐的把这个项目开发的控制权交给我们了,所以我开始有一些按自己的方式开发的机会了。因而最近动手开始重构这些字符串拼接的代码。

XMLJava Bean的解析框架,熟悉一点的只有DigesterXStreamDigester貌似只能从XML文件解析成Java Bean对象,所以只能选择XStream来做了,而且同组的其他项目也有在用XStream。一直听说XStream的使用比较简单,而且我对ThoughtWorks这家公司一直比较有好感,所以还以为引入XStream不会花太多时间,然而使用以后才发现XStream并没有想象的你那么简单。不过这个也有可能是因为我不想改变原来的XML数据格式,而之前的XML数据格式的设计自然不会考虑到如何便利的使用XStream。因而记录在使用过程中遇到的问题,供后来人参考,也为自己以后如果打算开其源码提供参考。废话就到这里了,接下来步入正题。

首先对于简单的引用,XStream使用起来确实比较简单,比如自定义标签的属性、使用属性和使用子标签的定义等:

@XStreamAlias("request")
public class XmlRequest1 {
    
private static XStream xstream;
    
static {
        xstream 
= new XStream();
        xstream.autodetectAnnotations(
true);
    }
   
    @XStreamAsAttribute
    
private String from;
   
    @XStreamAsAttribute
    @XStreamAlias(
"calculate-method")
    
private String calculateMethod;
   
    @XStreamAlias(
"request-time")
    private Date requestTime;
 
    @XStreamAlias(
"input-files")
    
private List<InputFileInfo> inputFiles;
   
    
public static String toXml(XmlRequest1 request) {
        StringWriter writer 
= new StringWriter();
        writer.append(Constants.XML_HEADER);
        xstream.toXML(request, writer);
        
return writer.toString();
    }
    
public static XmlRequest1 toInstance(String xmlContent) {
        
return (XmlRequest1)xstream.fromXML(xmlContent);
}

    @XStreamAlias(
"input-file")
    
public static class InputFileInfo {
        
private String type;
        
private String fileName;
        
    }
    
public static void main(String[] args) {
        XmlRequest1 request 
= buildXmlRequest();
        System.out.println(XmlRequest1.toXml(request));
    }
    
private static XmlRequest1 buildXmlRequest() {
        
    }
}

 对以上Request定义,我们可以得到如下结果:

<?xml version="1.0" encoding="UTF-8"?>
<request from="levin@host" calculate-method="advanced">
 
<request-time>2012-11-28 17:11:54.664 UTC</request-time>
 
<input-files>
    
<input-file>
      
<type>DATA</type>
      
<fileName>data.2012.11.29.dat</fileName>
    
</input-file>
    
<input-file>
      
<type>CALENDAR</type>
      
<fileName>calendar.2012.11.29.dat</fileName>
    
</input-file>
 
</input-files>
</request>

可惜这个世界不会那么清净,这个格式有些时候貌似并不符合要求,比如request-time的格式、input-files的格式,我们实际需要的格式是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<request from="levin@host" calculate-method="advanced">
 
<request-time>20121128T17:51:05</request-time>
 
<input-file type="DATA">data.2012.11.29.dat</input-file>
 
<input-file type="CALENDAR">calendar.2012.11.29.dat</input-file>
</request>

对不同Date格式的支持可以是用Converter实现,在XStream中默认使用自己实现的DateConverter,它支持的格式是:yyyy-MM-dd HH:mm:ss.S 'UTC',然而我们现在需要的格式是yyyy-MM-dd’T’HH:mm:ss,如果使用XStream直接注册DateConverter,可以使用配置自己的DateConverter,但是由于DateConverter的构造函数的定义以及@XStreamConverter的构造函数参数的支持方式的限制,貌似DateConverter不能很好的支持注解方式的注册,因而我时间了一个自己的DateConverter以支持注解:

public class LevinDateConverter extends DateConverter {
    
public LevinDateConverter(String dateFormat) {
        
super(dateFormat, new String[] { dateFormat });
    }
}

requestTime字段中需要加入以下注解定义:

@XStreamConverter(value=LevinDateConverter.class, strings={"yyyyMMdd'T'HH:mm:ss"})
@XStreamAlias(
"request-time")
private Date requestTime;

对集合类,XStream提供了@XStreamImplicit注解,以将集合中的内容摊平到上一层XML元素中,其中itemFieldName的值为其使用的标签名,此时InputFileInfo类中不需要@XStreamAlias标签的定义:

@XStreamImplicit(itemFieldName="input-file")
private List<InputFileInfo> inputFiles;

InputFileInfo中的字段,type作为属性很容易,只要为它加上@XStreamAsAttribute注解即可,而将fileName作为input-file标签的一个内容字符串,则需要使用ToAttributedValueConverter,其中Converter的参数为需要作为字符串内容的字段名:

@XStreamConverter(value=ToAttributedValueConverter.class, strings={"fileName"})
public static class InputFileInfo {
    @XStreamAsAttribute
    
private String type;
private String fileName;

}

XStream对枚举类型的支持貌似不怎么好,默认注册的EnumSingleValueConverter只是使用了Enum提供的name()和静态的valueOf()方法将enum转换成String或将String转换回enum。然而有些时候XML的字符串和类定义的enum值并不完全匹配,最常见的就是大小写的不匹配,此时需要写自己的Converter。在这种情况下,我一般会在enum中定义一个name属性,这样就可以自定义enum的字符串表示。比如有TimePeriodenum

public enum TimePeriod {
    MONTHLY(
"monthly"), WEEKLY("weekly"), DAILY("daily");
   
    
private String name;
   
    
public String getName() {
        
return name;
    }
   
    
private TimePeriod(String name) {
        
this.name = name;
    }
   
    
public static TimePeriod toEnum(String timePeriod) {
        
try {
            
return Enum.valueOf(TimePeriod.class, timePeriod);
        } 
catch(Exception ex) {
            
for(TimePeriod period : TimePeriod.values()) {
                
if(period.getName().equalsIgnoreCase(timePeriod)) {
                    
return period;
                }
            }
            
throw new IllegalArgumentException("Cannot convert <" + timePeriod + "> to TimePeriod enum");
        }
    }
}

我们可以编写以下Converter以实现对枚举类型的更宽的容错性:

public class LevinEnumSingleNameConverter extends EnumSingleValueConverter {
    
private static final String CUSTOM_ENUM_NAME_METHOD = "getName";
    
private static final String CUSTOM_ENUM_VALUE_OF_METHOD = "toEnum";
   
    
private Class<? extends Enum<?>> enumType;
 
    
public LevinEnumSingleNameConverter(Class<? extends Enum<?>> type) {
        
super(type);
        
this.enumType = type;
    }
 
    @Override
    
public String toString(Object obj) {
        Method method 
= getCustomEnumNameMethod();
        
if(method == null) {
            
return super.toString(obj);
        } 
else {
            
try {
                
return (String)method.invoke(obj, (Object[])null);
            } 
catch(Exception ex) {
                
return super.toString(obj);
            }
        }
    }
 
    @Override
    
public Object fromString(String str) {
        Method method 
= getCustomEnumStaticValueOfMethod();
        
if(method == null) {
            
return enhancedFromString(str);
        }
        
try {
            
return method.invoke(null, str);
        } 
catch(Exception ex) {
            
return enhancedFromString(str);
        }
    }
   
    
private Method getCustomEnumNameMethod() {
        
try {
            
return enumType.getMethod(CUSTOM_ENUM_NAME_METHOD, (Class<?>[])null);
        } 
catch(Exception ex) {
            
return null;
        }
    }
   
    
private Method getCustomEnumStaticValueOfMethod() {
        
try {
            Method method 
= enumType.getMethod(CUSTOM_ENUM_VALUE_OF_METHOD, (Class<?>[])null);
            
if(method.getModifiers() == Modifier.STATIC) {
                
return method;
            }
            
return null;
        } 
catch(Exception ex) {
            
return null;
        }
    }
   
    
private Object enhancedFromString(String str) {
        
try {
            
return super.fromString(str);
        } 
catch(Exception ex) {
            
for(Enum<?> item : enumType.getEnumConstants()) {
                
if(item.name().equalsIgnoreCase(str)) {
                    
return item;
                }
            }
            
throw new IllegalStateException("Cannot converter <" + str + "> to enum <" + enumType + ">");
        }
    }
}

如下方式使用即可:

@XStreamAsAttribute
@XStreamAlias(
"time-period")
@XStreamConverter(value
=LevinEnumSingleNameConverter.class)
private TimePeriod timePeriod;

double类型,貌似默认的DoubleConverter实现依然不给力,它不支持自定义的格式,比如我们想在序列化的时候用一下格式: ###,##0.0########,此时又需要编写自己的Converter

public class FormatableDoubleConverter extends DoubleConverter {
    
private String pattern;
    
private DecimalFormat formatter;
   
    
public FormatableDoubleConverter(String pattern) {
        
this.pattern = pattern;
        
this.formatter = new DecimalFormat(pattern);
    }
   
    @Override
    
public String toString(Object obj) {
        
if(formatter == null) {
            
return super.toString(obj);
        } 
else {
            
return formatter.format(obj);
        }
    }
   
    @Override
    
public Object fromString(String str) {
        
try {
            
return super.fromString(str);
        } 
catch(Exception ex) {
            
if(formatter != null) {
                
try {
                    
return formatter.parse(str);
                } 
catch(Exception e) {
                    
throw new IllegalArgumentException("Cannot parse <" + str + "> to double value", e);
                }
            }
            
throw new IllegalArgumentException("Cannot parse <" + str + "> to double value", ex);
        }
    }
   
    
public String getPattern() {
        
return pattern;
    }
}

使用方式和之前的Converter类似:

@XStreamAsAttribute
@XStreamConverter(value
=FormatableDoubleConverter.class, strings={"###,##0.0########"})
private double value;

最后,还有两个XStream没法实现的,或者说我没有找到一个更好的实现方式的场景。第一种场景是XStream不能很好的处理对象组合问题:

在面向对象编程中,一般尽量的倾向于抽取相同的数据成一个类,而通过组合的方式构建整个数据结构。比如Student类中有nameaddressAddress是一个类,它包含citycodestreet等信息,此时如果要对Student对象做如下格式序列化:

<student name=”Levin”>
 
<city>shanghai</city>
 
<street>zhangjiang</street>
 
<code>201203</code>
</student>

貌似我没有找到可以实现的方式,XStream能做是在中间加一层address标签。对这种场景的解决方案,一种是将Address中的属性平摊到Student类中,另一种是让Student继承自Address类。不过貌似这两种都不是比较理想的办法。

第二种场景是XStream不能很好的处理多态问题:

比如我们有一个Trade类,它可能表示不同的产品:

public class Trade {
    
private String tradeId;
    private Product product;

}
abstract class Product {
    
private String name;
    
public Product(String name) {
        
this.name = name;
}

}
class FX extends Product {
    
private double ratio;
    
public FX() {
        
super("fx");
    }
    
}
class Future extends Product {
    
private double maturity;
    
public Future() {
        
super("future");
    }
    
}

通过一些简单的设置,我们能得到如下XML格式:

<trades>
 
<trade trade-id="001">
    
<product class="levin.xstream.blog.FX" name="fx" ratio="0.59"/>
 
</trade>
 
<trade trade-id="002">
    
<product class="levin.xstream.blog.Future" name="future" maturity="2.123"/>
 
</trade>
</trades>

作为数据文件,对Java类的定义显然是不合理的,因而简单一些,我们可以编写自己的Converterclass属性从product中去除:

xstream.registerConverter(new ProductConverter(
        xstream.getMapper(), xstream.getReflectionProvider()));
 
    
public ProductConverter(Mapper mapper, ReflectionProvider reflectionProvider) {
        
super(mapper, reflectionProvider);
    }
   
    @Override
    
public boolean canConvert(@SuppressWarnings("rawtypes") Class type) {
        
return Product.class.isAssignableFrom(type);
    }
 
    @Override
    
protected Object instantiateNewInstance(HierarchicalStreamReader reader, UnmarshallingContext context) {
        Object currentObject 
= context.currentObject();
        
if(currentObject != null) {
            
return currentObject;
        }
       
        String name 
= reader.getAttribute("name");
        
if("fx".equals(name)) {
            
return reflectionProvider.newInstance(FX.class);
        } 
else if("future".equals(name)) {
            
return reflectionProvider.newInstance(Future.class);
        }
        
throw new IllegalStateException("Cannot convert <" + name + "> product");
    }
}

在所有Production上定义@XStreamAlias(“product”)注解。这时的XML输出结果为:

<trades>
 
<trade trade-id="001">
    
<product name="fx" ratio="0.59"/>
 
</trade>
 
<trade trade-id="002">
    
<product name="future" maturity="2.123"/>
 
</trade>
</trades>

然而如果有人希望XML的输出结果如下呢?

<trades>
 
<trade trade-id="001">
    
<fx ratio="0.59"/>
 
</trade>
 
<trade trade-id="002">
    
<future maturity="2.123"/>
 
</trade>
</trades>

大概找了一下,可能可以定义自己的Mapper来解决,不过XStream的源码貌似比较复杂,没有时间深究这个问题,留着以后慢慢解决吧。

补充:

对Map类型数据,XStream默认使用以下格式显示:

<map class="linked-hash-map">
    
<entry>
      
<string>key1</string>
      
<string>value1</string>
    
</entry>
    
<entry>
      
<string>key2</string>
      
<string>value2</string>
    
</entry>
 
</map>

 

但是对一些简单的Map,我们希望如下显示:

 <map>
    
<entry key="key1" value="value1"/>
    
<entry key="key2" value="value2"/>
 
</map>

 

对这种需求需要通过编写Converter解决,继承自MapConverter,覆盖以下函数,这里的Map默认key和value都是String类型,如果他们不是String类型,需要另外添加逻辑:

@SuppressWarnings("rawtypes")
@Override
public void marshal(Object source, HierarchicalStreamWriter writer,
        MarshallingContext context) {
    Map map 
= (Map) source;
    
for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
        Entry entry 
= (Entry) iterator.next();
        ExtendedHierarchicalStreamWriterHelper.startNode(writer, mapper()
                .serializedClass(Map.Entry.
class), entry.getClass());
 
        writer.addAttribute(
"key", entry.getKey().toString());
        writer.addAttribute(
"value", entry.getValue().toString());
        writer.endNode();
    }
}
 
@Override
@SuppressWarnings({ 
"unchecked""rawtypes" })
protected void putCurrentEntryIntoMap(HierarchicalStreamReader reader,
        UnmarshallingContext context, Map map, Map target) {
    Object key 
= reader.getAttribute("key");
    Object value 
= reader.getAttribute("value");
 
    target.put(key, value);
}

 

但是只是使用Converter,得到的结果多了一个class属性:

 <map class="linked-hash-map">
    
<entry key="key1" value="value1"/>
    
<entry key="key2" value="value2"/>
 
</map>

 

在XStream中,如果定义的字段是一个父类或接口,在序列化是会默认加入class属性以确定反序列化时用的类,为了去掉这个class属性,可以定义默认的实现类来解决(虽然感觉这种解决方案不太好,但是目前还没有找到更好的解决方案)。

 

xstream.addDefaultImplementation(LinkedHashMap.class, Map.class);
 
转自:http://www.blogjava.net/DLevin/archive/2012/11/30/392240.html
分享到:
评论

相关推荐

    使用XStream序列化/反序列化对象

    使用XStream序列化/反序列化对象

    xstream序列化与反序列化

    xstream中xml与javaBean的互转

    XStream <--> Java 对象序列化为 XML 互换

    XStream 是一种序列化工具而不是数据绑定工具,就是说不能从 XML 或者 XML Schema Definition (XSD) 文件生成类。 和其他序列化工具相比,XStream 有三个突出的特点: XStream 不关心序列化/逆序列化的类的字段的...

    XStream对象序列化

    使用 XStream 把 Java 对象序列化为 XML

    XStream XML与Json转换

    XStream在运行时使用Java反射机制对要进行序列化的对象树的结构进行探索,并不需要对对象作出修改。XStream可以序列化内部字段,包括私private和final字段,并且支持非公开类以及内部类。 在缺省情况下,XStream不...

    xstream反序列化流程分析1

    xstream反序列化流程分析测试demo代码如下所示:跟入 com.thoughtworks.xstream 的 fromXML 函数,接收前端传入的 Str

    XML操作类库XStream.zip

    Xstream是一种OXMapping 技术,是用来处理XML文件序列化的框架,在将javaBean序列化,或将XML文件反序列化的时候,不需要其它辅助类和映射文件,使得XML序列化不再繁琐。XStream使用起来非常简单,不需要预先生成相关...

    使用XStream操作xml教程

    XStream是以个实现序列化数据和xml之间相互转换的类库,使用非常简单、方便

    xstream实现实体类序列化转xml

    xstream-1.4.2.jar,xpp3_min-1.1.4.jar,xmlpull-1.1.3.1.jar 包括 //XML --&gt; JavaBean, //XML --&gt; JavaBean //JavaBean --&gt; JSON,//JSON--&gt; JavaBean

    使用 XStream 把 Java 对象序列化为 XML

    NULL 博文链接:https://dolphin-ygj.iteye.com/blog/567748

    XStream使用例子

    XStream使用例子,如何序列化、反序列化到XML和JSON格式。

    xml的序列化与验证

    1、xml序列化的Java3种实现方法 1)Serializable和Externalizable接口Xstream框架2)Simple框架 3)Apache的AXIOM框架 2、XML验证文档的生成工具 trang.jar 3、利用XSD文件的XML3种验证方法 1)Dom4j的SAXValidator ...

    xstream-1.4.7.jar及源码;xml-pull-1.3.1.jar

    因此XML常用于数据交换、对象序列化(这种序列化和Java对象的序列化技术有着本质的区别)。 Stream对象相当Java对象和XML之间的转换器,转换过程是双向的。创建XSteam对象的方式很简单,只需要new XStream()即可。...

    对象与XML相互转换xstream

    使用 XStream不用任何映射就能实现多数 Java 对象的序列化。 在生成的 XML 中对象名变成了元素名,类中的字符串组成了 XML中的元素内容 xstream-1.4.2.jar xpp3_min-1.1.4c.jar xmlpull-1.1.3.1.jar

    序列化成xml文件所必须的jar包

    包含这三个jar包:kxml2-2.3.0.jar、xmlpull-1.1.3.1.jar、xstream-1.4.3.jar。是将文件序列化成xml文件所必须的jar包

    xstream实现xml和java bean 互相转换

    XStream:XStream是一个简单易用的Java库,用于序列化和反序列化对象。它可以将Java对象转换为XML文档,也可以将XML文档转换为Java对象。 pom引用 &lt;groupId&gt;com.thoughtworks.xstream&lt;/groupId&gt; &lt;artifactId&gt;...

    xstream-1.4.15.jar

    Xstream 是 Java 类库,用来将对象序列化成 XML (JSON) 或反序列化为对象。XStream 是一款开源软件,允许在 BSD 许可证的许可下分发。 0x01 漏洞描述 Xstream上次对CVE-2020-26217处理并不彻底,虽然通过黑名单...

    Xstream操作Xml与Json.doc

    Xstream是一种OXMapping 技术,是用来处理XML文件序列化的框架,在将JavaBean序列化,或将XML文件反序列化的时候,不需要其它辅助类和映射文件,使得XML序列化不再繁索。Xstream也可以将JavaBean序列化成Json或反序列...

    基于Java的Struts2 XStream插件设计源码

    Struts2 XStream插件:基于Java开发,包含20个文件,包括12个Java类文件...该项目是一个Struts2插件,可以输出JSON或XML格式数据,使用Xstream库进行序列化和反序列化,旨在为开发者提供一个高效、便捷的数据处理工具。

    XStream所需jar包

    Xstream是一种OXMapping 技术,是用来处理XML文件序列化的框架,在将JavaBean序列化,或将XML文件反序列化的时候,不需要其它辅助类和映射文件,使得XML序列化不再繁索。Xstream也可以将JavaBean序列化成Json或反序列...

Global site tag (gtag.js) - Google Analytics