昨天作一個項目時用到了XStream來作XML到Bean的轉換器,須要轉換的Bean格式以下:java
@Data @XStreamAlias("Document") public class AccountTradeHistoryResponseVo { @XStreamAlias("ResponseHeader") private CommonResponseHeader header; @XStreamAlias("Content") private List<AccountTradeHistoryDetail> content; }
本覺得一切順利,結果卻報了個意料以外的異常:node
java.lang.ClassCastException: com.xinzhen.pay.vo.jj.powercore.response.AccountTradeHistoryDetail cannot be cast to com.xinzhen.pay.vo.jj.powercore.response.AccountTradeHistoryDetail
明明是同一個類,怎麼就轉換異常了呢,百思不得其解!app
XStream提供了Converter
接口能夠用來自定義轉換器,接口定義以下:ide
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之間到底有什麼貓膩。this
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<AccountTradeHistoryDetail>,很明顯是泛型參數 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的轉換器。code
直接實現Converter這個接口,canConvert()方法返回true,直接接手整個Document的解析工做。對象
public class CustomConverter implements Converter { // 根結點下的成員變量類型 private Map<String, Class> rootTypeMap; // 根結點下List成員類型(若泛型 T=List<Item>, 也應該放在listItemType裏) private Map<String, Class> listItemMap; // 根結點下的成員變量字段 private Map<String, Field> rootFieldMap; // 要解析的類型實例(ROOT) private Object instance; /** * @param instanceType 要解析的實例類型 * @param typeMap 泛型<成員變量名, 類型>Map * @param listItemType List類型<成員變量名, 類型>Map * @throws Exception */ public CustomConverter(Class instanceType, Map<String, Class> typeMap, Map<String, Class> 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<String> while (reader.hasMoreChildren()) { reader.moveDown(); list.add(reader.getValue()); reader.moveUp(); } } else { // List<T> 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<String, Field> 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<String, Class> typeMap
: 泛型類型的字段名和實際類型的MapMap<String, Class> listItemType
: List類型的字段名和實際類型的Map
雖然功能簡單,但至少知足了目前轉換的需求;有關XStream類加載的問題,有時間還得好好研究。