xstream自定义转换_XStream自定义XML转换器

本文介绍了在使用XStream进行XML到Bean转换时遇到的ClassCastException问题,分析了转换器的责任链模式,并展示了如何通过自定义Converter解决类加载导致的异常。最后给出自定义Converter的实现代码,用于解析XML到Bean。
摘要由CSDN通过智能技术生成

莫名其妙的异常

昨天做一个项目时用到了XStream来做XML到Bean的转换器,需要转换的Bean格式如下:

@Data

@XStreamAlias("Document")

public class AccountTradeHistoryResponseVo {

@XStreamAlias("ResponseHeader")

private CommonResponseHeader header;

@XStreamAlias("Content")

private List content;

}

本以为一切顺利,结果却报了个意料之外的异常:

java.lang.ClassCastException: com.xinzhen.pay.vo.jj.powercore.response.AccountTradeHistoryDetail cannot be cast to com.xinzhen.pay.vo.jj.powercore.response.AccountTradeHistoryDetail

明明是同一个类,怎么就转换异常了呢,百思不得其解!

Converter链

XStream提供了Converter接口可以用来自定义转换器,接口定义如下:

public interface Converter extends ConverterMatcher {

// Bean -> XML/Json

void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context);

// XML/Json -> Bean

Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context);

}

public interface ConverterMatcher {

// 是否支持clazz类型的转换

boolean canConvert(Class clazz);

}

Converter的设计使用了责任链模式,类似于SpringMVC的ViewResolvers链,通过canConverter()方法判断是否支持该元素类型的转换,如果支持则调用这个Converter的marshal()或unmarshal()来做Bean到XML/Json之间的转换;否则转移到下一个注册的Converter继续判断流程。

先简单继承了一下AbstractCollectionConverter,然后在解析的时候注册这个Converter,查看一下这里的Class之间到底有什么猫腻。

public class CustomCollectionConverter extends AbstractCollectionConverter {

public CustomCollectionConverter(Mapper mapper) {

super(mapper);

}

@Override

public boolean canConvert(Class clazz) {

Class> clazz1 = AccountTradeHistoryDetail.class;

System.out.println(clazz1 == clazz);

ClassLoader classLoader1 = clazz.getClassLoader();

ClassLoader classLoader2 = clazz1.getClassLoader();

return clazz1 == clazz;

}

@Override

public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {

}

@Override

public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {

return null;

}

}

果然不出所料,当传进来的clazz是AccountTradeHistoryDetail.class时,跟clazz1竟然不是同一个Class对象,两个ClassLoader也不相同,一个是RestartClassLoader, 另一个是AppClassLoader;因为项目是使用SpringBoot构建的,有两个ClassLoader是正常的,但为什么AccountTradeHistoryDetail.class这个类会被这两个ClassLoader分别加载一次呢?为了排除SpringBoot本身的问题,于是又写了个方法测试了一下:

Class> clazz = AccountTradeHistoryDetail.class;

Field f = AccountTradeHistoryResponseVo.class.getDeclaredField("content");

// content为List,很明显是泛型参数

ParameterizedType t = (ParameterizedType) f.getGenericType();

Type[] types = t.getActualTypeArguments();

Class> clazz1 = (Class) types[0]; // 第一个类型就是实际泛型类型

System.out.println(clazz == clazz1);

这个地方为true,说明在这里这两个AccountTradeHistoryDetail是同一个Class对象,那么就可以排除SpringBoot的问题;看来是XStream出于什么原因重新加载了这个类,但是明明可以通过反射从字段中得出实际的参数类型,不知道XStream为什么要这么做。跟进XStream解析的源码,没找到加载Class的地方,时间紧迫,也没时间去仔细阅读文档,于是干脆自己动手重写了一个简单的从XML到Bean的转换器。

自定义Converter

直接实现Converter这个接口,canConvert()方法返回true,直接接手整个Document的解析工作。

public class CustomConverter implements Converter {

// 根结点下的成员变量类型

private Map rootTypeMap;

// 根结点下List成员类型(若泛型 T=List, 也应该放在listItemType里)

private Map listItemMap;

// 根结点下的成员变量字段

private Map rootFieldMap;

// 要解析的类型实例(ROOT)

private Object instance;

/**

* @param instanceType 要解析的实例类型

* @param typeMap 泛型Map

* @param listItemType List类型Map

* @throws Exception

*/

public CustomConverter(Class instanceType, Map typeMap, Map listItemType) throws Exception {

instance = instanceType.newInstance();

this.rootTypeMap = typeMap == null ? new HashMap<>() : rootTypeMap;

this.listItemMap = listItemType == null ? new HashMap<>() : listItemType;

rootFieldMap = new HashMap<>();

Field[] fields = instanceType.getDeclaredFields();

for (Field field : fields) {

XStreamAlias annotation = field.getAnnotation(XStreamAlias.class);

// 字段名, 如果设置了别名则使用别名

String fieldName = annotation == null ? field.getName() : annotation.value();

rootFieldMap.put(fieldName, field);

}

}

@Override

public void marshal(Object obj, HierarchicalStreamWriter writer, MarshallingContext context) {

}

@Override

public Object unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) {

try {

// Root下节点处理

while (reader.hasMoreChildren()) {

reader.moveDown();

String nodeName = reader.getNodeName();

Field field = rootFieldMap.get(nodeName);

if (field == null) {

reader.moveUp();

continue;

}

Class type = rootTypeMap.get(nodeName);

if (type == null) {

type = field.getType();

}

field.setAccessible(true);

// 该节点为List类型

if (listItemMap.containsKey(nodeName)) {

List list = new ArrayList();

Class itemType = listItemMap.get(nodeName);

if (itemType == String.class) { // List

while (reader.hasMoreChildren()) {

reader.moveDown();

list.add(reader.getValue());

reader.moveUp();

}

} else { // List

while (reader.hasMoreChildren()) {

reader.moveDown();

list.add(parseObject(itemType, reader));

reader.moveUp();

}

}

field.set(instance, list);

} else if (type == String.class) { // 该节点为String类型, 直接设置value

field.set(instance, reader.getValue());

} else { // 非String类型, 解析该节点

field.set(instance, parseObject(type, reader));

}

reader.moveUp();

}

} catch (Exception e) {

e.printStackTrace();

return null;

}

return instance;

}

/**

* 解析子节点: 子节点只能是非基本类型(包括String)

*

* @param type

* @param reader

* @return

*/

public Object parseObject(Class type, HierarchicalStreamReader reader) throws Exception {

Object obj = type.newInstance();

Map fieldMap = new HashMap<>();

Field[] fields = type.getDeclaredFields();

for (Field field : fields) {

XStreamAlias annotation = field.getAnnotation(XStreamAlias.class);

// 字段名, 如果设置了别名则使用别名

String fieldName = annotation == null ? field.getName() : annotation.value();

fieldMap.put(fieldName, field);

}

while (reader.hasMoreChildren()) {

reader.moveDown();

String nodeName = reader.getNodeName();

// 获取对应的字段

Field field = fieldMap.get(nodeName);

if (field == null) {

reader.moveUp();

continue;

}

Class fType = field.getType();

field.setAccessible(true);

if (fType == String.class) { // String类型, 直接设置value

field.set(obj, reader.getValue());

} else { // 其他类型, 继续解析

field.set(obj, parseObject(fType, reader));

}

reader.moveUp();

}

return obj;

}

/**

* 这个Converter作为所有字段的Converter

*

* @param type

* @return

*/

@Override

public boolean canConvert(Class type) {

return true;

}

}

该注册器的构造方法有几个关键参数:

Class instanceType: 要转换的目标类型

Map typeMap: 泛型类型的字段名和实际类型的Map

Map listItemType: List类型的字段名和实际类型的Map

虽然功能简单,但至少满足了目前转换的需求;有关XStream类加载的问题,有时间还得好好研究。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值