摘要:跟進(jìn)解析的源碼,沒(méi)找到加載的地方,時(shí)間緊迫,也沒(méi)時(shí)間去仔細(xì)閱讀文檔,于是干脆自己動(dòng)手重寫(xiě)了一個(gè)簡(jiǎn)單的從到的轉(zhuǎn)換器。自定義直接實(shí)現(xiàn)這個(gè)接口,方法返回,直接接手整個(gè)的解析工作。
莫名其妙的異常
昨天做一個(gè)項(xiàng)目時(shí)用到了XStream來(lái)做XML到Bean的轉(zhuǎn)換器,需要轉(zhuǎn)換的Bean格式如下:
@Data @XStreamAlias("Document") public class AccountTradeHistoryResponseVo { @XStreamAlias("ResponseHeader") private CommonResponseHeader header; @XStreamAlias("Content") private Listcontent; }
本以為一切順利,結(jié)果卻報(bào)了個(gè)意料之外的異常:
java.lang.ClassCastException: com.xinzhen.pay.vo.jj.powercore.response.AccountTradeHistoryDetail cannot be cast to com.xinzhen.pay.vo.jj.powercore.response.AccountTradeHistoryDetail
明明是同一個(gè)類(lèi),怎么就轉(zhuǎn)換異常了呢,百思不得其解!
Converter鏈XStream提供了Converter接口可以用來(lái)自定義轉(zhuǎn)換器,接口定義如下:
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類(lèi)型的轉(zhuǎn)換 boolean canConvert(Class clazz); }
Converter的設(shè)計(jì)使用了責(zé)任鏈模式,類(lèi)似于SpringMVC的ViewResolvers鏈,通過(guò)canConverter()方法判斷是否支持該元素類(lèi)型的轉(zhuǎn)換,如果支持則調(diào)用這個(gè)Converter的marshal()或unmarshal()來(lái)做Bean到XML/Json之間的轉(zhuǎn)換;否則轉(zhuǎn)移到下一個(gè)注冊(cè)的Converter繼續(xù)判斷流程。
先簡(jiǎn)單繼承了一下AbstractCollectionConverter,然后在解析的時(shí)候注冊(cè)這個(gè)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; } }
果然不出所料,當(dāng)傳進(jìn)來(lái)的clazz是AccountTradeHistoryDetail.class時(shí),跟clazz1竟然不是同一個(gè)Class對(duì)象,兩個(gè)ClassLoader也不相同,一個(gè)是RestartClassLoader, 另一個(gè)是AppClassLoader;因?yàn)轫?xiàng)目是使用SpringBoot構(gòu)建的,有兩個(gè)ClassLoader是正常的,但為什么AccountTradeHistoryDetail.class這個(gè)類(lèi)會(huì)被這兩個(gè)ClassLoader分別加載一次呢?為了排除SpringBoot本身的問(wèn)題,于是又寫(xiě)了個(gè)方法測(cè)試了一下:
Class> clazz = AccountTradeHistoryDetail.class; Field f = AccountTradeHistoryResponseVo.class.getDeclaredField("content"); // content為L(zhǎng)ist,很明顯是泛型參數(shù) ParameterizedType t = (ParameterizedType) f.getGenericType(); Type[] types = t.getActualTypeArguments(); Class> clazz1 = (Class) types[0]; // 第一個(gè)類(lèi)型就是實(shí)際泛型類(lèi)型 System.out.println(clazz == clazz1);
這個(gè)地方為true,說(shuō)明在這里這兩個(gè)AccountTradeHistoryDetail是同一個(gè)Class對(duì)象,那么就可以排除SpringBoot的問(wèn)題;看來(lái)是XStream出于什么原因重新加載了這個(gè)類(lèi),但是明明可以通過(guò)反射從字段中得出實(shí)際的參數(shù)類(lèi)型,不知道XStream為什么要這么做。跟進(jìn)XStream解析的源碼,沒(méi)找到加載Class的地方,時(shí)間緊迫,也沒(méi)時(shí)間去仔細(xì)閱讀文檔,于是干脆自己動(dòng)手重寫(xiě)了一個(gè)簡(jiǎn)單的從XML到Bean的轉(zhuǎn)換器。
自定義Converter直接實(shí)現(xiàn)Converter這個(gè)接口,canConvert()方法返回true,直接接手整個(gè)Document的解析工作。
public class CustomConverter implements Converter { // 根結(jié)點(diǎn)下的成員變量類(lèi)型 private MaprootTypeMap; // 根結(jié)點(diǎn)下List成員類(lèi)型(若泛型 T=List - , 也應(yīng)該放在listItemType里) private Map
listItemMap; // 根結(jié)點(diǎn)下的成員變量字段 private Map rootFieldMap; // 要解析的類(lèi)型實(shí)例(ROOT) private Object instance; /** * @param instanceType 要解析的實(shí)例類(lèi)型 * @param typeMap 泛型<成員變量名, 類(lèi)型>Map * @param listItemType List類(lèi)型<成員變量名, 類(lèi)型>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); // 字段名, 如果設(shè)置了別名則使用別名 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下節(jié)點(diǎn)處理 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); // 該節(jié)點(diǎn)為L(zhǎng)ist類(lèi)型 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) { // 該節(jié)點(diǎn)為String類(lèi)型, 直接設(shè)置value field.set(instance, reader.getValue()); } else { // 非String類(lèi)型, 解析該節(jié)點(diǎn) field.set(instance, parseObject(type, reader)); } reader.moveUp(); } } catch (Exception e) { e.printStackTrace(); return null; } return instance; } /** * 解析子節(jié)點(diǎn): 子節(jié)點(diǎn)只能是非基本類(lèi)型(包括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); // 字段名, 如果設(shè)置了別名則使用別名 String fieldName = annotation == null ? field.getName() : annotation.value(); fieldMap.put(fieldName, field); } while (reader.hasMoreChildren()) { reader.moveDown(); String nodeName = reader.getNodeName(); // 獲取對(duì)應(yīng)的字段 Field field = fieldMap.get(nodeName); if (field == null) { reader.moveUp(); continue; } Class fType = field.getType(); field.setAccessible(true); if (fType == String.class) { // String類(lèi)型, 直接設(shè)置value field.set(obj, reader.getValue()); } else { // 其他類(lèi)型, 繼續(xù)解析 field.set(obj, parseObject(fType, reader)); } reader.moveUp(); } return obj; } /** * 這個(gè)Converter作為所有字段的Converter * * @param type * @return */ @Override public boolean canConvert(Class type) { return true; } }
該注冊(cè)器的構(gòu)造方法有幾個(gè)關(guān)鍵參數(shù):
Class instanceType: 要轉(zhuǎn)換的目標(biāo)類(lèi)型
Map
typeMap : 泛型類(lèi)型的字段名和實(shí)際類(lèi)型的MapMap
listItemType : List類(lèi)型的字段名和實(shí)際類(lèi)型的Map
雖然功能簡(jiǎn)單,但至少滿(mǎn)足了目前轉(zhuǎn)換的需求;有關(guān)XStream類(lèi)加載的問(wèn)題,有時(shí)間還得好好研究。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/110369.html
摘要:跟進(jìn)解析的源碼,沒(méi)找到加載的地方,時(shí)間緊迫,也沒(méi)時(shí)間去仔細(xì)閱讀文檔,于是干脆自己動(dòng)手重寫(xiě)了一個(gè)簡(jiǎn)單的從到的轉(zhuǎn)換器。自定義直接實(shí)現(xiàn)這個(gè)接口,方法返回,直接接手整個(gè)的解析工作。 莫名其妙的異常 昨天做一個(gè)項(xiàng)目時(shí)用到了XStream來(lái)做XML到Bean的轉(zhuǎn)換器,需要轉(zhuǎn)換的Bean格式如下: @Data @XStreamAlias(Document) public class AccountT...
摘要:簡(jiǎn)介是一個(gè)對(duì)象與互相轉(zhuǎn)換的工具類(lèi)庫(kù)。官網(wǎng)鏈接簡(jiǎn)單使用下載頁(yè)面使用構(gòu)建項(xiàng)目的加入以下依賴(lài)創(chuàng)建對(duì)象轉(zhuǎn)使用方法。創(chuàng)建解析對(duì)象設(shè)置別名默認(rèn)會(huì)輸出全路徑轉(zhuǎn)為轉(zhuǎn)換后的文本為轉(zhuǎn)對(duì)象使用方法。 XStream簡(jiǎn)介 XStream是一個(gè)Java對(duì)象與XML互相轉(zhuǎn)換的工具類(lèi)庫(kù)。 官網(wǎng)鏈接: http://x-stream.github.io/index.html 簡(jiǎn)單使用 下載頁(yè)面:http://x-st...
摘要:最受歡迎的個(gè)庫(kù)連續(xù)兩年,二度成為中最受歡迎的庫(kù)。此外,谷歌的開(kāi)源項(xiàng)目來(lái)勢(shì)洶洶,勇奪第三名,該庫(kù)包含了一系列谷歌內(nèi)含的核心庫(kù)。在本次最受歡迎的個(gè)庫(kù)中,個(gè)庫(kù)與相關(guān)。 【編者按】本文作者為 Henn Idan,主要介紹基于 GitHub 中的數(shù)據(jù)分析,得出的2016年度最受歡迎的100個(gè) Java 庫(kù)。本文系國(guó)內(nèi) ITOM 管理平臺(tái) OneAPM 編譯呈現(xiàn)。 誰(shuí)拔得頭籌?誰(shuí)又落于人后?我們分...
摘要:想要實(shí)現(xiàn)自定義菜單的功能,需要有已認(rèn)證訂閱號(hào)和已認(rèn)證服務(wù)號(hào)。測(cè)試時(shí)可以嘗試取消關(guān)注公眾賬號(hào)后再次關(guān)注,則可以看到創(chuàng)建后的效果。 想要實(shí)現(xiàn)自定義菜單的功能,需要有已認(rèn)證訂閱號(hào)和已認(rèn)證服務(wù)號(hào)。對(duì)于測(cè)試開(kāi)發(fā)來(lái)說(shuō),可以直接申請(qǐng)一個(gè)測(cè)試賬號(hào):http://mp.weixin.qq.com/debug... 同樣需要token的驗(yàn)證,前期接口已經(jīng)定義好了,直接拿來(lái)就可以 showImg(https...
閱讀 2905·2021-11-15 18:02
閱讀 3800·2021-10-14 09:43
閱讀 3732·2021-09-08 10:41
閱讀 2522·2019-08-30 15:53
閱讀 1803·2019-08-30 14:14
閱讀 1943·2019-08-29 16:12
閱讀 3137·2019-08-29 14:03
閱讀 1280·2019-08-29 13:46