XStream把xml文件转化为java对象(转)

前端之家收集整理的这篇文章主要介绍了XStream把xml文件转化为java对象(转)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

原作者:http://www.blogjava.net/DLevin/archive/2012/11/30/392240.html

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

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

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @XStreamAlias( " request " )
@H_502_88@public @H_502_88@class XmlRequest1 {
@H_502_88@private @H_502_88@static XStream xstream;
@H_502_88@static {
xstream
= @H_502_88@new XStream();
xstream.autodetectAnnotations(
@H_502_88@true );
}

@XStreamAsAttribute
@H_502_88@private String from;

@XStreamAsAttribute
@XStreamAlias(
" calculate-method " )
@H_502_88@private String calculateMethod;

@XStreamAlias(
" request-time " )
@H_502_88@ private Date requestTime;

@XStreamAlias(
" input-files " )
@H_502_88@private List < InputFileInfo > inputFiles;

@H_502_88@public @H_502_88@static String toXml(XmlRequest1 request) {
StringWriter writer
= @H_502_88@new StringWriter();
writer.append(Constants.XML_HEADER);
xstream.toXML(request,writer);
@H_502_88@return writer.toString();
}
@H_502_88@public @H_502_88@static XmlRequest1 toInstance(String xmlContent) {
@H_502_88@return (XmlRequest1)xstream.fromXML(xmlContent);
}

@XStreamAlias(
" input-file " )
@H_502_88@public @H_502_88@static @H_502_88@class InputFileInfo {
@H_502_88@private String type;
@H_502_88@private String fileName;

}
@H_502_88@public @H_502_88@static @H_502_88@void main(String[] args) {
XmlRequest1 request
= buildXmlRequest();
System.out.println(XmlRequest1.toXml(request));
}
@H_502_88@private @H_502_88@static XmlRequest1 buildXmlRequest() {

}
}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@<? xml version="1.0" encoding="UTF-8" @H_502_88@?>
@H_502_88@< request from @H_502_88@="levin@host" calculate-method @H_502_88@="advanced" @H_502_88@>
@H_502_88@< request-time @H_502_88@> 2012-11-28 17:11:54.664 UTC @H_502_88@</ request-time @H_502_88@>
@H_502_88@< input-files @H_502_88@>
@H_502_88@< input-file @H_502_88@>
@H_502_88@< type @H_502_88@> DATA @H_502_88@</ type @H_502_88@>
@H_502_88@< fileName @H_502_88@> data.2012.11.29.dat @H_502_88@</ fileName @H_502_88@>
@H_502_88@</ input-file @H_502_88@>
@H_502_88@< input-file @H_502_88@>
@H_502_88@< type @H_502_88@> CALENDAR @H_502_88@</ type @H_502_88@>
@H_502_88@< fileName @H_502_88@> calendar.2012.11.29.dat @H_502_88@</ fileName @H_502_88@>
@H_502_88@</ input-file @H_502_88@>
@H_502_88@</ input-files @H_502_88@>
@H_502_88@</ request @H_502_88@>

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@<? xml version="1.0" encoding="UTF-8" @H_502_88@?>
@H_502_88@< request from @H_502_88@="levin@host" calculate-method @H_502_88@="advanced" @H_502_88@>
@H_502_88@< request-time @H_502_88@> 20121128T17:51:05 @H_502_88@</ request-time @H_502_88@>
@H_502_88@< input-file type @H_502_88@="DATA" @H_502_88@> data.2012.11.29.dat @H_502_88@</ input-file @H_502_88@>
@H_502_88@< input-file type @H_502_88@="CALENDAR" @H_502_88@> calendar.2012.11.29.dat @H_502_88@</ input-file @H_502_88@>
@H_502_88@</ request @H_502_88@>

对不同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支持注解:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@public @H_502_88@class LevinDateConverter @H_502_88@extends DateConverter {
@H_502_88@public LevinDateConverter(String dateFormat) {
@H_502_88@super (dateFormat, @H_502_88@new String[] { dateFormat });
}
}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @XStreamConverter(value = LevinDateConverter. @H_502_88@class ,strings = { " yyyyMMdd'T'HH:mm:ss " })
@XStreamAlias(
" request-time " )
@H_502_88@private Date requestTime;

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @XStreamImplicit(itemFieldName = " input-file " )
@H_502_88@private List < InputFileInfo > inputFiles;

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @XStreamConverter(value = ToAttributedValueConverter. @H_502_88@class ,strings = { " fileName " })
@H_502_88@public @H_502_88@static @H_502_88@class InputFileInfo {
@XStreamAsAttribute
@H_502_88@private String type;
@H_502_88@private String fileName;

}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@public @H_502_88@enum TimePeriod {
MONTHLY(
" monthly " ),WEEKLY( " weekly " ),DAILY( " daily " );

@H_502_88@private String name;

@H_502_88@public String getName() {
@H_502_88@return name;
}

@H_502_88@private TimePeriod(String name) {
@H_502_88@this .name = name;
}

@H_502_88@public @H_502_88@static TimePeriod toEnum(String timePeriod) {
@H_502_88@try {
@H_502_88@return Enum.valueOf(TimePeriod. @H_502_88@class ,timePeriod);
}
@H_502_88@catch (Exception ex) {
@H_502_88@for (TimePeriod period : TimePeriod.values()) {
@H_502_88@if (period.getName().equalsIgnoreCase(timePeriod)) {
@H_502_88@return period;
}
}
@H_502_88@throw @H_502_88@new IllegalArgumentException( " Cannot convert < " + timePeriod + " > to TimePeriod enum " );
}
}
}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@public @H_502_88@class LevinEnumSingleNameConverter @H_502_88@extends EnumSingleValueConverter {
@H_502_88@private @H_502_88@static @H_502_88@final String CUSTOM_ENUM_NAME_METHOD = " getName " ;
@H_502_88@private @H_502_88@static @H_502_88@final String CUSTOM_ENUM_VALUE_OF_METHOD = " toEnum " ;

@H_502_88@private Class <? @H_502_88@extends Enum <?>> enumType;

@H_502_88@public LevinEnumSingleNameConverter(Class <? @H_502_88@extends Enum <?>> type) {
@H_502_88@super (type);
@H_502_88@this .enumType = type;
}

@Override
@H_502_88@public String toString(Object obj) {
Method method
= getCustomEnumNameMethod();
@H_502_88@if (method == @H_502_88@null ) {
@H_502_88@return @H_502_88@super .toString(obj);
}
@H_502_88@else {
@H_502_88@try {
@H_502_88@return (String)method.invoke(obj,(Object[]) @H_502_88@null );
}
@H_502_88@catch (Exception ex) {
@H_502_88@return @H_502_88@super .toString(obj);
}
}
}

@Override
@H_502_88@public Object fromString(String str) {
Method method
= getCustomEnumStaticValueOfMethod();
@H_502_88@if (method == @H_502_88@null ) {
@H_502_88@return enhancedFromString(str);
}
@H_502_88@try {
@H_502_88@return method.invoke( @H_502_88@null ,str);
}
@H_502_88@catch (Exception ex) {
@H_502_88@return enhancedFromString(str);
}
}

@H_502_88@private Method getCustomEnumNameMethod() {
@H_502_88@try {
@H_502_88@return enumType.getMethod(CUSTOM_ENUM_NAME_METHOD,(Class <?> []) @H_502_88@null );
}
@H_502_88@catch (Exception ex) {
@H_502_88@return @H_502_88@null ;
}
}

@H_502_88@private Method getCustomEnumStaticValueOfMethod() {
@H_502_88@try {
Method method
= enumType.getMethod(CUSTOM_ENUM_VALUE_OF_METHOD,(Class <?> []) @H_502_88@null );
@H_502_88@if (method.getModifiers() == Modifier.STATIC) {
@H_502_88@return method;
}
@H_502_88@return @H_502_88@null ;
}
@H_502_88@catch (Exception ex) {
@H_502_88@return @H_502_88@null ;
}
}

@H_502_88@private Object enhancedFromString(String str) {
@H_502_88@try {
@H_502_88@return @H_502_88@super .fromString(str);
}
@H_502_88@catch (Exception ex) {
@H_502_88@for (Enum <?> item : enumType.getEnumConstants()) {
@H_502_88@if (item.name().equalsIgnoreCase(str)) {
@H_502_88@return item;
}
}
@H_502_88@throw @H_502_88@new IllegalStateException( " Cannot converter < " + str + " > to enum < " + enumType + " > " );
}
}
}

如下方式使用即可:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @XStreamAsAttribute
@XStreamAlias(
" time-period " )
@XStreamConverter(value
= LevinEnumSingleNameConverter. @H_502_88@class )
@H_502_88@private TimePeriod timePeriod;

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@public @H_502_88@class FormatableDoubleConverter @H_502_88@extends DoubleConverter {
@H_502_88@private String pattern;
@H_502_88@private DecimalFormat formatter;

@H_502_88@public FormatableDoubleConverter(String pattern) {
@H_502_88@this .pattern = pattern;
@H_502_88@this .formatter = @H_502_88@new DecimalFormat(pattern);
}

@Override
@H_502_88@public String toString(Object obj) {
@H_502_88@if (formatter == @H_502_88@null ) {
@H_502_88@return @H_502_88@super .toString(obj);
}
@H_502_88@else {
@H_502_88@return formatter.format(obj);
}
}

@Override
@H_502_88@public Object fromString(String str) {
@H_502_88@try {
@H_502_88@return @H_502_88@super .fromString(str);
}
@H_502_88@catch (Exception ex) {
@H_502_88@if (formatter != @H_502_88@null ) {
@H_502_88@try {
@H_502_88@return formatter.parse(str);
}
@H_502_88@catch (Exception e) {
@H_502_88@throw @H_502_88@new IllegalArgumentException( " Cannot parse < " + str + " > to double value " ,e);
}
}
@H_502_88@throw @H_502_88@new IllegalArgumentException( " Cannot parse < " + str + " > to double value " ,ex);
}
}

@H_502_88@public String getPattern() {
@H_502_88@return pattern;
}
}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @XStreamAsAttribute
@XStreamConverter(value
= FormatableDoubleConverter. @H_502_88@class ,strings = { " ###,##0.0######## " })
@H_502_88@private @H_502_88@double value;

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

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@< student name @H_502_88@=”Levin”>
<city @H_502_88@> shanghai @H_502_88@</ city @H_502_88@>
@H_502_88@< street @H_502_88@> zhangjiang @H_502_88@</ street @H_502_88@>
@H_502_88@< code @H_502_88@> 201203 @H_502_88@</ code @H_502_88@>
@H_502_88@</ student @H_502_88@>

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

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

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@public @H_502_88@class Trade {
@H_502_88@private String tradeId;
@H_502_88@ private Product product;

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

}
@H_502_88@class FX @H_502_88@extends Product {
@H_502_88@private @H_502_88@double ratio;
@H_502_88@public FX() {
@H_502_88@super ( " fx " );
}

}
@H_502_88@class Future @H_502_88@extends Product {
@H_502_88@private @H_502_88@double maturity;
@H_502_88@public Future() {
@H_502_88@super ( " future " );
}

}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@< trades @H_502_88@>
@H_502_88@< trade trade-id @H_502_88@="001" @H_502_88@>
@H_502_88@< product class @H_502_88@="levin.xstream.blog.FX" name @H_502_88@="fx" ratio @H_502_88@="0.59" @H_502_88@/>
@H_502_88@</ trade @H_502_88@>
@H_502_88@< trade trade-id @H_502_88@="002" @H_502_88@>
@H_502_88@< product class @H_502_88@="levin.xstream.blog.Future" name @H_502_88@="future" maturity @H_502_88@="2.123" @H_502_88@/>
@H_502_88@</ trade @H_502_88@>
@H_502_88@</ trades @H_502_88@>

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> xstream.registerConverter( @H_502_88@new ProductConverter(
xstream.getMapper(),xstream.getReflectionProvider()));

@H_502_88@public ProductConverter(Mapper mapper,ReflectionProvider reflectionProvider) {
@H_502_88@super (mapper,reflectionProvider);
}

@Override
@H_502_88@public @H_502_88@boolean canConvert(@SuppressWarnings( " rawtypes " ) Class type) {
@H_502_88@return Product. @H_502_88@class .isAssignableFrom(type);
}

@Override
@H_502_88@protected Object instantiateNewInstance(HierarchicalStreamReader reader,UnmarshallingContext context) {
Object currentObject
= context.currentObject();
@H_502_88@if (currentObject != @H_502_88@null ) {
@H_502_88@return currentObject;
}

String name
= reader.getAttribute( " name " );
@H_502_88@if ( " fx " .equals(name)) {
@H_502_88@return reflectionProvider.newInstance(FX. @H_502_88@class );
}
@H_502_88@else @H_502_88@if ( " future " .equals(name)) {
@H_502_88@return reflectionProvider.newInstance(Future. @H_502_88@class );
}
@H_502_88@throw @H_502_88@new IllegalStateException( " Cannot convert < " + name + " > product " );
}
}

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@< trades @H_502_88@>
@H_502_88@< trade trade-id @H_502_88@="001" @H_502_88@>
@H_502_88@< product name @H_502_88@="fx" ratio @H_502_88@="0.59" @H_502_88@/>
@H_502_88@</ trade @H_502_88@>
@H_502_88@< trade trade-id @H_502_88@="002" @H_502_88@>
@H_502_88@< product name @H_502_88@="future" maturity @H_502_88@="2.123" @H_502_88@/>
@H_502_88@</ trade @H_502_88@>
@H_502_88@</ trades @H_502_88@>

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@< trades @H_502_88@>
@H_502_88@< trade trade-id @H_502_88@="001" @H_502_88@>
@H_502_88@< fx ratio @H_502_88@="0.59" @H_502_88@/>
@H_502_88@</ trade @H_502_88@>
@H_502_88@< trade trade-id @H_502_88@="002" @H_502_88@>
@H_502_88@< future maturity @H_502_88@="2.123" @H_502_88@/>
@H_502_88@</ trade @H_502_88@>
@H_502_88@</ trades @H_502_88@>

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

补充:

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

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

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@< map @H_502_88@>
@H_502_88@< entry key @H_502_88@="key1" value @H_502_88@="value1" @H_502_88@/>
@H_502_88@< entry key @H_502_88@="key2" value @H_502_88@="value2" @H_502_88@/>
@H_502_88@</ map @H_502_88@>

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @SuppressWarnings( " rawtypes " )
@Override
@H_502_88@public @H_502_88@void marshal(Object source,HierarchicalStreamWriter writer,
MarshallingContext context) {
Map map
= (Map) source;
@H_502_88@for (Iterator iterator = map.entrySet().iterator(); iterator.hasNext();) {
Entry entry
= (Entry) iterator.next();
ExtendedHierarchicalStreamWriterHelper.startNode(writer,mapper()
.serializedClass(Map.Entry.
@H_502_88@class ),entry.getClass());

writer.addAttribute(
" key " ,entry.getKey().toString());
writer.addAttribute(
" value " ,entry.getValue().toString());
writer.endNode();
}
}

@Override
@SuppressWarnings({
" unchecked " , " rawtypes " })
@H_502_88@protected @H_502_88@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属性

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> @H_502_88@< map class @H_502_88@="linked-hash-map" @H_502_88@>
@H_502_88@< entry key @H_502_88@="key1" value @H_502_88@="value1" @H_502_88@/>
@H_502_88@< entry key @H_502_88@="key2" value @H_502_88@="value2" @H_502_88@/>
@H_502_88@</ map @H_502_88@>

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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> xstream.addDefaultImplementation(LinkedHashMap. @H_502_88@class ,Map. @H_502_88@class );
原文链接:https://www.f2er.com/xml/299872.html

猜你在找的XML相关文章