摘要:跟進解析的源碼,沒找到加載的地方,時間緊迫,也沒時間去仔細閱讀文檔,于是干脆自己動手重寫了一個簡單的從到的轉換器。自定義直接實現這個接口,方法返回,直接接手整個的解析工作。
莫名其妙的異常
昨天做一個項目時用到了XStream來做XML到Bean的轉換器,需要轉換的Bean格式如下:
@Data @XStreamAlias("Document") public class AccountTradeHistoryResponseVo { @XStreamAlias("ResponseHeader") private CommonResponseHeader header; @XStreamAlias("Content") private Listcontent; }
本以為一切順利,結果卻報了個意料之外的異常:
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 MaprootTypeMap; // 根結點下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 : 泛型類型的字段名和實際類型的MapMap
listItemType : List類型的字段名和實際類型的Map
雖然功能簡單,但至少滿足了目前轉換的需求;有關XStream類加載的問題,有時間還得好好研究。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/110369.html
摘要:跟進解析的源碼,沒找到加載的地方,時間緊迫,也沒時間去仔細閱讀文檔,于是干脆自己動手重寫了一個簡單的從到的轉換器。自定義直接實現這個接口,方法返回,直接接手整個的解析工作。 莫名其妙的異常 昨天做一個項目時用到了XStream來做XML到Bean的轉換器,需要轉換的Bean格式如下: @Data @XStreamAlias(Document) public class AccountT...
摘要:簡介是一個對象與互相轉換的工具類庫。官網鏈接簡單使用下載頁面使用構建項目的加入以下依賴創建對象轉使用方法。創建解析對象設置別名默認會輸出全路徑轉為轉換后的文本為轉對象使用方法。 XStream簡介 XStream是一個Java對象與XML互相轉換的工具類庫。 官網鏈接: http://x-stream.github.io/index.html 簡單使用 下載頁面:http://x-st...
摘要:最受歡迎的個庫連續兩年,二度成為中最受歡迎的庫。此外,谷歌的開源項目來勢洶洶,勇奪第三名,該庫包含了一系列谷歌內含的核心庫。在本次最受歡迎的個庫中,個庫與相關。 【編者按】本文作者為 Henn Idan,主要介紹基于 GitHub 中的數據分析,得出的2016年度最受歡迎的100個 Java 庫。本文系國內 ITOM 管理平臺 OneAPM 編譯呈現。 誰拔得頭籌?誰又落于人后?我們分...
摘要:想要實現自定義菜單的功能,需要有已認證訂閱號和已認證服務號。測試時可以嘗試取消關注公眾賬號后再次關注,則可以看到創建后的效果。 想要實現自定義菜單的功能,需要有已認證訂閱號和已認證服務號。對于測試開發來說,可以直接申請一個測試賬號:http://mp.weixin.qq.com/debug... 同樣需要token的驗證,前期接口已經定義好了,直接拿來就可以 showImg(https...
閱讀 2914·2021-11-15 18:02
閱讀 3808·2021-10-14 09:43
閱讀 3748·2021-09-08 10:41
閱讀 2527·2019-08-30 15:53
閱讀 1809·2019-08-30 14:14
閱讀 1954·2019-08-29 16:12
閱讀 3150·2019-08-29 14:03
閱讀 1284·2019-08-29 13:46