现在参与的项目是一个纯Application Server,整个Server都是自己搭建的,使用JMS消息实现客户端和服务器的交互,交互的数据格式采用XML。说来惭愧,开始为了赶进度,所有XML消息都是使用字符串拼接的,而XML的解析则是使用DOM方式查找的。我很早就看这些代码不爽了,可惜一直没有时间去重构,最近项目加了几个人,而且美国那边也开始渐渐的把这个项目开发的控制权交给我们了,所以我开始有一些按自己的方式开发的机会了。因而最近动手开始重构这些字符串拼接的代码。
对XML到Java Bean的解析框架,熟悉一点的只有Digester和XStream,Digester貌似只能从XML文件解析成Java Bean对象,所以只能选择XStream来做了,而且同组的其他项目也有在用XStream。一直听说XStream的使用比较简单,而且我对ThoughtWorks这家公司一直比较有好感,所以还以为引入XStream不会花太多时间,然而使用以后才发现XStream并没有想象的你那么简单。不过这个也有可能是因为我不想改变原来的XML数据格式,而之前的XML数据格式的设计自然不会考虑到如何便利的使用XStream。因而记录在使用过程中遇到的问题,供后来人参考,也为自己以后如果打算开其源码提供参考。废话就到这里了,接下来步入正题。
首先对于简单的引用,XStream使用起来确实比较简单,比如自定义标签的属性、使用属性和使用子标签的定义等:
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定义,我们可以得到如下结果:
<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的格式,我们实际需要的格式是这样的:
<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 LevinDateConverter(String dateFormat) {
super(dateFormat, new String[] { dateFormat });
}
}
在requestTime字段中需要加入以下注解定义:
@XStreamAlias("request-time")
private Date requestTime;
对集合类,XStream提供了@XStreamImplicit注解,以将集合中的内容摊平到上一层XML元素中,其中itemFieldName的值为其使用的标签名,此时InputFileInfo类中不需要@XStreamAlias标签的定义:
private List<InputFileInfo> inputFiles;
对InputFileInfo中的字段,type作为属性很容易,只要为它加上@XStreamAsAttribute注解即可,而将fileName作为input-file标签的一个内容字符串,则需要使用ToAttributedValueConverter,其中Converter的参数为需要作为字符串内容的字段名:
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的字符串表示。比如有TimePeriod的enum:
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以实现对枚举类型的更宽的容错性:
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 + ">");
}
}
}
如下方式使用即可:
@XStreamAlias("time-period")
@XStreamConverter(value=LevinEnumSingleNameConverter.class)
private TimePeriod timePeriod;
对double类型,貌似默认的DoubleConverter实现依然不给力,它不支持自定义的格式,比如我们想在序列化的时候用一下格式:” ###,##0.0########”,此时又需要编写自己的Converter:
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类似:
@XStreamConverter(value=FormatableDoubleConverter.class, strings={"###,##0.0########"})
private double value;
最后,还有两个XStream没法实现的,或者说我没有找到一个更好的实现方式的场景。第一种场景是XStream不能很好的处理对象组合问题:
在面向对象编程中,一般尽量的倾向于抽取相同的数据成一个类,而通过组合的方式构建整个数据结构。比如Student类中有name、address,Address是一个类,它包含city、code、street等信息,此时如果要对Student对象做如下格式序列化:
<city>shanghai</city>
<street>zhangjiang</street>
<code>201203</code>
</student>
貌似我没有找到可以实现的方式,XStream能做是在中间加一层address标签。对这种场景的解决方案,一种是将Address中的属性平摊到Student类中,另一种是让Student继承自Address类。不过貌似这两种都不是比较理想的办法。
第二种场景是XStream不能很好的处理多态问题:
比如我们有一个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格式:
<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类的定义显然是不合理的,因而简单一些,我们可以编写自己的Converter将class属性从product中去除:
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输出结果为:
<trade trade-id="001">
<product name="fx" ratio="0.59"/>
</trade>
<trade trade-id="002">
<product name="future" maturity="2.123"/>
</trade>
</trades>
然而如果有人希望XML的输出结果如下呢?
<trade trade-id="001">
<fx ratio="0.59"/>
</trade>
<trade trade-id="002">
<future maturity="2.123"/>
</trade>
</trades>
大概找了一下,可能可以定义自己的Mapper来解决,不过XStream的源码貌似比较复杂,没有时间深究这个问题,留着以后慢慢解决吧。
补充:
对Map类型数据,XStream默认使用以下格式显示:
<entry>
<string>key1</string>
<string>value1</string>
</entry>
<entry>
<string>key2</string>
<string>value2</string>
</entry>
</map>
但是对一些简单的Map,我们希望如下显示:
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
对这种需求需要通过编写Converter解决,继承自MapConverter,覆盖以下函数,这里的Map默认key和value都是String类型,如果他们不是String类型,需要另外添加逻辑:
@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属性:
<entry key="key1" value="value1"/>
<entry key="key2" value="value2"/>
</map>
在XStream中,如果定义的字段是一个父类或接口,在序列化是会默认加入class属性以确定反序列化时用的类,为了去掉这个class属性,可以定义默认的实现类来解决(虽然感觉这种解决方案不太好,但是目前还没有找到更好的解决方案)。
相关推荐
// 反序列化XML字符串回Person对象 Person deserializedPerson = (Person) xstream.fromXML(xml); // 检查反序列化的对象是否与原始对象一致 System.out.println(deserializedPerson.getName() + ", " + ...
小负荷测试中,创建了一个包含各种数据类型的`Person`实例,然后对其进行了序列化和反序列化。 在序列化测试中,Bboss和Xstream的表现各有优劣。对于小负荷数据,Bboss在1000次执行中耗时78毫秒,而Xstream耗时218...
以下是如何使用XStream进行序列化和反序列化的简单步骤: 1. **序列化对象**: 为了序列化对象,你需要创建一个`XStream`实例,然后使用`toXML`方法将对象转换为XML字符串。例如,在`Writer`类中,可以创建一个`...
xstream中xml与javaBean的互转
- 安全性:由于XStream可以直接反序列化XML到任意对象,可能存在安全风险,因此在处理不受信任的输入时应谨慎。 博客链接可能提供了更多关于如何使用XStream的实际示例和最佳实践。在实际项目中,了解并熟练掌握...
5. 反序列化XML:如果有一个XML字符串,你可以通过调用`fromXML()`方法将其转换回Java对象。 ```java String xml = ... // 之前序列化的XML字符串 XStream xstream = new XStream(); Person deserializedPerson = ...
XStream 是一种序列化工具而不是数据绑定工具,就是说不能从 XML 或者 XML Schema Definition (XSD) 文件生成类。 和其他序列化工具相比,XStream 有三个突出的特点: XStream 不关心序列化/逆序列化的类的字段的...
在本文中,我们将深入探讨XStream库的反序列化流程,这是一个流行的Java库,用于将XML数据转换为Java对象和反之亦然。XStream的反序列化过程涉及到多个步骤,从接收XML字符串到最终创建Java对象。以下是详细的分析:...
5. **反序列化XML到Java对象**:将XML字符串转换回`Person`对象。 ```java try { Person deserializedPerson = mapper.readValue(xmlString, Person.class); Log.d("XMLDeserialization", "Name: " + ...
在Java开发中,数据序列化和反序列化是常见的需求,特别是在处理XML格式的数据时。XStream是一个强大的库,它提供了将Java对象转换为XML,以及将XML转换回等效Java对象的功能。在这个场景中,"使用XStream多集合生成...
在压缩包中的"xml"文件可能是示例的XML数据,用于演示如何使用XStream进行序列化和反序列化操作。实际应用中,开发者可以根据这些知识对XML数据进行读取、解析和存储,提升开发效率和代码可读性。
Java中的xStream库提供了一个简单易用的方式来将Java对象序列化为XML,反之亦然,即反序列化XML回Java对象。 **xStream简介** xStream是一个开源的Java库,由Johannes Link创建。它提供了简洁、直观的API来处理XML...
这样,当我们使用Xstream序列化一个`Person`对象时,会得到如下的XML: ```xml <firstName>John <lastName>Doe ``` 为了序列化Java对象,我们可以创建一个Xstream实例并调用`toXML()`方法: ```java Person ...
总的来说,“使用XStream是实现XML与Java对象的转换(6)--持久化”这个主题涵盖了如何使用XStream进行对象的持久化操作,包括创建XStream实例、序列化和反序列化,以及文件I/O操作。了解这些知识点对于需要在Java项目...
【作品名称】:基于 Java 的亿赛通电子文档安全管理系统XStream反序列化漏洞任意文件上传利用 【适用人群】:适用于希望学习不同... -e,--encode <arg> 加密XML反序列化payload -h,--help 打印帮助信息 -p,--proxy
然而,Java的标准序列化机制并不支持直接将对象序列化为XML格式,而是默认生成二进制格式的数据。在实验7中,我们看到了如何使用第三方库XStream将Java对象转换为XML。 XStream是一个用于在Java对象和XML之间进行...
XStream通过消除XML配置,使得XML绑定变得更加直观,这在处理大量XML数据时尤其方便。 **一、XStream的基本用法** 1. **安装与引入** 要使用 XStream,首先需要将其添加到你的项目依赖中。如果你使用的是 Maven,...
// 反序列化XML字符串回对象 Person deserializedPerson = (Person) xstream.fromXML(xml); System.out.println(deserializedPerson); } } class Person { private String firstName; private String ...
在Java开发中,数据的序列化与反序列化是一个常见的需求,这有助于在持久化存储、网络传输等场景中方便地处理对象。XStream库提供了一个优雅的方式来将Java对象转换为XML,反之亦然,使得处理XML数据变得更加简单。...
XStream在运行时使用Java反射机制对要进行序列化的对象树的结构进行探索,并不需要对对象作出修改。XStream可以序列化内部字段,包括私private和final字段,并且支持非公开类以及内部类。 在缺省情况下,XStream不...