转载:http://www.blogjava.net/DLevin/archive/2012/11/30/392240.html
首先对于简单的引用,XStream使用起来确实比较简单,比如自定义标签的属性、使用属性和使用子标签的定义等:
public class XmlRequest1{
private static XStreamxstream;
{
xstream = new XStream();
xstream.autodetectAnnotations( true );
}
@XStreamAsAttribute
Stringfrom;
@XStreamAsAttribute
@XStreamAlias( calculate-method StringcalculateMethod;
@XStreamAlias( request-time private DaterequestTime;
@XStreamAlias( input-files List < InputFileInfo > inputFiles;
StringtoXml(XmlRequest1request){
StringWriterwriter StringWriter();
writer.append(Constants.XML_HEADER);
xstream.toXML(request,writer);
return writer.toString();
}
XmlRequest1toInstance(StringxmlContent){
(XmlRequest1)xstream.fromXML(xmlContent);
}
@XStreamAlias( input-file InputFileInfo{
Stringtype;
StringfileName;
}
void main(String[]args){
XmlRequest1request buildXmlRequest();
System.out.println(XmlRequest1.toXml(request));
}
XmlRequest1buildXmlRequest(){
}
}
对以上Request定义,我们可以得到如下结果:
< request from ="levin@host" ="advanced" > 2012-11-2817:11:54.664UTC </ type DATA fileName data.2012.11.29.dat CALENDAR calendar.2012.11.29.dat >
可惜这个世界不会那么清净,这个格式有些时候貌似并不符合要求,比如request-time的格式、input-files的格式,我们实际需要的格式是这样的:
LevinDateConverter(StringdateFormat){
super (dateFormat, String[]{dateFormat});
}
}
在requestTime字段中需要加入以下注解定义:
@XStreamAlias( DaterequestTime;
对集合类,XStream提供了@XStreamImplicit注解,以将集合中的内容摊平到上一层XML元素中,其中itemFieldName的值为其使用的标签名,此时InputFileInfo类中不需要@XStreamAlias标签的定义:
对InputFileInfo中的字段,type作为属性很容易,只要为它加上@XStreamAsAttribute注解即可,而将fileName作为input-file标签的一个内容字符串,则需要使用ToAttributedValueConverter,其中Converter的参数为需要作为字符串内容的字段名:
InputFileInfo{
@XStreamAsAttribute
StringfileName;
}
XStream对枚举类型的支持貌似不怎么好,默认注册的EnumSingleValueConverter只是使用了Enum提供的name()和静态的valueOf()方法将enum转换成String或将String转换回enum。然而有些时候XML的字符串和类定义的enum值并不完全匹配,最常见的就是大小写的不匹配,此时需要写自己的Converter。在这种情况下,我一般会在enum中定义一个name属性,这样就可以自定义enum的字符串表示。比如有TimePeriod的enum:
MONTHLY( monthly ),WEEKLY( weekly daily );
Stringname;
StringgetName(){
name;
}
TimePeriod(Stringname){
this .name TimePeriodtoEnum(StringtimePeriod){
try {
Enum.valueOf(TimePeriod. catch (Exceptionex){
for (TimePeriodperiod:TimePeriod.values()){
if (period.getName().equalsIgnoreCase(timePeriod)){
period;
}
}
throw IllegalArgumentException( Cannotconvert< + timePeriod >toTimePeriodenum );
}
}
}
我们可以编写以下Converter以实现对枚举类型的更宽的容错性:
final StringCUSTOM_ENUM_NAME_METHOD getName ;
StringCUSTOM_ENUM_VALUE_OF_METHOD toEnum ;
Class <? Enum <?>> enumType;
LevinEnumSingleNameConverter(Class type){
(type);
.enumType type;
}
@Override
StringtoString(Objectobj){
Methodmethod getCustomEnumNameMethod();
(method == null ){
.toString(obj);
} else (String)method.invoke(obj,(Object[]) );
} .toString(obj);
}
}
}
@Override
ObjectfromString(Stringstr){
Methodmethod getCustomEnumStaticValueOfMethod();
enhancedFromString(str);
}
method.invoke( enhancedFromString(str);
}
}
MethodgetCustomEnumNameMethod(){
enumType.getMethod(CUSTOM_ENUM_NAME_METHOD,(Class <?> []) ;
}
}
MethodgetCustomEnumStaticValueOfMethod(){
{
Methodmethod enumType.getMethod(CUSTOM_ENUM_VALUE_OF_METHOD,0)">);
(method.getModifiers() Modifier.STATIC){
method;
}
;
} ObjectenhancedFromString(Stringstr){
.fromString(str);
} (Enum item:enumType.getEnumConstants()){
(item.name().equalsIgnoreCase(str)){
item;
}
}
IllegalStateException( Cannotconverter< str >toenum< enumType 如下方式使用即可:
@XStreamAlias( time-period )
@XStreamConverter(value LevinEnumSingleNameConverter. TimePeriodtimePeriod;
对double类型,貌似默认的DoubleConverter实现依然不给力,它不支持自定义的格式,比如我们想在序列化的时候用一下格式:”###,##0.0########”,此时又需要编写自己的Converter:
Stringpattern;
DecimalFormatformatter;
FormatableDoubleConverter(Stringpattern){
.pattern pattern;
.formatter DecimalFormat(pattern);
}
@Override
StringtoString(Objectobj){
(formatter formatter.format(obj);
}
}
@Override
ObjectfromString(Stringstr){
!= formatter.parse(str);
} (Exceptione){
Cannotparse< >todoublevalue }
}
StringgetPattern(){
pattern;
}
}
使用方式和之前的Converter类似:
@XStreamConverter(value FormatableDoubleConverter. ###,##0.0######## double value;
最后,还有两个XStream没法实现的,或者说我没有找到一个更好的实现方式的场景。第一种场景是XStream不能很好的处理对象组合问题:
在面向对象编程中,一般尽量的倾向于抽取相同的数据成一个类,而通过组合的方式构建整个数据结构。比如Student类中有name、address,Address是一个类,它包含city、code、street等信息,此时如果要对Student对象做如下格式序列化:
<city shanghai city street zhangjiang code 201203 student 貌似我没有找到可以实现的方式,XStream能做是在中间加一层address标签。对这种场景的解决方案,一种是将Address中的属性平摊到Student类中,另一种是让Student继承自Address类。不过貌似这两种都不是比较理想的办法。
第二种场景是XStream不能很好的处理多态问题:
比如我们有一个Trade类,它可能表示不同的产品:
StringtradeId;
Productproduct;
}
abstract Product{
Stringname;
Product(Stringname){
name;
}
}
FX ratio;
FX(){
( fx );
}
}
Future maturity;
Future(){
future );
}
}
通过一些简单的设置,我们能得到如下XML格式:
作为数据文件,对Java类的定义显然是不合理的,因而简单一些,我们可以编写自己的Converter将class属性从product中去除:
xstream.getMapper(),xstream.getReflectionProvider()));
ProductConverter(Mappermapper,ReflectionProviderreflectionProvider){
(mapper,reflectionProvider);
}
@Override
boolean canConvert(@SuppressWarnings( rawtypes )Classtype){
Product. .isAssignableFrom(type);
}
@Override
protected ObjectinstantiateNewInstance(HierarchicalStreamReaderreader,UnmarshallingContextcontext){
ObjectcurrentObject context.currentObject();
(currentObject currentObject;
}
Stringname reader.getAttribute( .equals(name)){
reflectionProvider.newInstance(FX. reflectionProvider.newInstance(Future. );
}
>product );
}
}
在所有Production上定义@XStreamAlias(“product”)注解。这时的XML输出结果为:
补充:
对Map类型数据,XStream默认使用以下格式显示:
entry string key1 </ value1 key2 value2 map >
但是对一些简单的Map,我们希望如下显示:
对这种需求需要通过编写Converter解决,继承自MapConverter,覆盖以下函数,这里的Map默认key和value都是String类型,如果他们不是String类型,需要另外添加逻辑:
@Override
public void marshal(Objectsource,HierarchicalStreamWriterwriter,
MarshallingContextcontext){
Mapmap = (Map)source;
for (Iteratoriterator map.entrySet().iterator();iterator.hasNext();){
Entryentry (Entry)iterator.next();
ExtendedHierarchicalStreamWriterHelper.startNode(writer,mapper()
.serializedClass(Map.Entry. class writer.addAttribute( unchecked })
protected putCurrentEntryIntoMap(HierarchicalStreamReaderreader,
UnmarshallingContextcontext,Mapmap,Maptarget){
Objectkey reader.getAttribute( );
Objectvalue );
target.put(key,value);
}
但是只是使用Converter,得到的结果多了一个class属性:
在XStream中,如果定义的字段是一个父类或接口,在序列化是会默认加入class属性以确定反序列化时用的类,为了去掉这个class属性,可以定义默认的实现类来解决(虽然感觉这种解决方案不太好,但是目前还没有找到更好的解决方案)。