使用jvm sandbox對三層嵌套類型的改造示例
問題背景
先簡單介紹下基于jvm-sandbox的imock工具,是Java方法級別的mock,操作就是監(jiān)聽指定方法,返回指定的mock內(nèi)容。
jvm-sandbox 利用字節(jié)碼操作和自定義類加載器的技術(shù),將原始方法替換為模擬代碼,從而在應(yīng)用程序中實(shí)現(xiàn)方法級別的模擬。這種方法非常強(qiáng)大,但也需要對字節(jié)碼操作、類加載機(jī)制和 JVM 內(nèi)部原理有一定的理解。
公司要搭建一個(gè)方法級別的后端mock平臺,因此我在imock的基礎(chǔ)上進(jìn)行二次開發(fā)進(jìn)行使用。
問題描述
在mock某個(gè)三方接口的方法時(shí)遇到報(bào)錯(cuò):
ava.lang.ClassCastException: com.alibaba.fastjson.JSONObject cannot be cast to com.travelsky.angeldoe.output.PassengerFlightInfo
看樣子是本來應(yīng)該是JSONObject 無法轉(zhuǎn)化成PassengerFlightInfo類型,通過日志排查問題,定位到報(bào)錯(cuò)代碼。
PassengerFlightInfo?passengerFlightInfo?=?JSON.parseObject(out ?.getPassengerFlightInfoList().get(0).toString(),?PassengerFlightInfo.class);
線上服務(wù)沒有報(bào)錯(cuò),測試mock環(huán)境報(bào)錯(cuò),那么顯然是數(shù)據(jù)的問題,通過Arthas追蹤方法返回的bean對比發(fā)現(xiàn),差異就是線上的PassengerFlightInfo是一個(gè)bean,測試的PassengerFlightInfo是一個(gè)object。差異由此出現(xiàn)。


那么問題的關(guān)鍵就在于,如何通過mock工具把object提前轉(zhuǎn)成bean。
解決方案
改造mock agent工具思路:通過我們的mock-module.jar實(shí)現(xiàn)。
- 根據(jù)PsrInfoOutputBean初步解析returnObject,獲取list中的object
- 將object解析成PassengerFlightInfo,再通過反射技術(shù)將bean反射回PsrInfoOutputBean
代碼實(shí)現(xiàn)
//針對cki特殊類型PsrInfoOutputBean
case?3:
????//獲取advice返回類型的類加載器
????ClassLoader?behaviorClassLoader?=?advice.getBehavior().getReturnType().getClassLoader();
????//加載最外層PsrInfoOutputBean
????Class<?>?targetClass?=?behaviorClassLoader.loadClass(ro.getClassNames()[0]);
????LogUtil.info2("targetClass=",?targetClass.toString());
????//根據(jù)目標(biāo)類解析returnData
????Object?res1?=?JSON.parseObject(ro.getReturnData(),?targetClass);
????//賦值保存做對比
????Object?res0?=?res1;
????LogUtil.info2("res1-before=",?res1.toString());
????//?通過反射獲取passengerFlightInfoList
????List<Object>?passengerFlightInfoList?=?(List<Object>)?targetClass.getMethod("getPassengerFlightInfoList").invoke(res1);
????LogUtil.info2("passengerFlightInfoList=",?passengerFlightInfoList.toString());
????if?(!passengerFlightInfoList.isEmpty())?{
????????//?獲取?passengerFlightInfoList?列表中的第一個(gè)元素
????????Object?firstPassengerFlightInfoList?=?passengerFlightInfoList.get(0);
????????LogUtil.info2("firstPassengerFlightInfoList=",?firstPassengerFlightInfoList.toString());
????????//?將?firstFlightInfo?轉(zhuǎn)換成?JSON?字符串
????????String?firstFlightInfoJson?=?JSON.toJSONString(firstPassengerFlightInfoList);
????????//?獲取第三層額外目標(biāo)?Bean?類的類名,使用同一類加載器
????????Class<?>?targetBeanClass?=?behaviorClassLoader.loadClass(ro.getClassNames()[2]);
????????LogUtil.info2("targetBeanClass=",?targetBeanClass.toString());
????????//根據(jù)類解析成bean
????????Object?targetBean?=?JSON.parseObject(firstFlightInfoJson,?targetBeanClass);
????????LogUtil.info2("targetBean=",?targetBean.toString());
????????//?創(chuàng)建一個(gè)新的passengerFlightInfoListNew?將?targetBean?添加到?passengerFlightInfoList?中
????????List<Object>?passengerFlightInfoListNew?=?new?ArrayList<>();
????????passengerFlightInfoListNew.add(targetBean);
????????//?設(shè)置?passengerFlightInfoList?屬性回?res1
????????try?{
????????????//?執(zhí)行反射方法,把passengerFlightInfoListNew反射回res
????????????Method?method?=?targetClass.getMethod("setPassengerFlightInfoList",?List.class);
????????????method.invoke(res1,?passengerFlightInfoListNew);
????????}?catch?(Exception?e)?{
????????????//?捕獲異常并打印日志
????????????LogUtil.info2("Error?occurred?while?invoking?method:=",?e.getMessage()+"|"+e);
????????}
????}
????LogUtil.info2("前后的兩個(gè)類equals嗎?=",?String.valueOf(res1.equals(res0)));
????LogUtil.info2("res1-after=",?res1.toString());
????ProcessController.returnImmediately(res1);
????break;遇到的坑
外部獲取的類名不能直接通過Class.forName加載,如下代碼所示:
?//?使用目標(biāo)?Bean?類名解析?JSON?字符串成目標(biāo)?Bean ????????Class<?>?targetBeanClass?=?Class.forName(targetBeanClassName);
實(shí)際會報(bào)錯(cuò):"message": "com.taobao.rigel.rap.model.PsrInfoOutputBean cannot be cast to com.taobao.rigel.rap.model.PsrInfoOutputBean", 原因是這兩個(gè)bean雖然名字一樣,但是類加載器不同,就導(dǎo)致bean的實(shí)際是不一樣的。類是否相同可以用equals進(jìn)行判斷。
因此正確的做法是,先獲取advice返回類型的類加載器,然后加載我們所需要的類,這樣業(yè)務(wù)的代碼就會認(rèn)得我們的bean了。
???//獲取advice返回類型的類加載器 ????ClassLoader?behaviorClassLoader?=?advice.getBehavior().getReturnType().getClassLoader(); ????//加載最外層PsrInfoOutputBean ????Class<?>?targetClass?=?behaviorClassLoader.loadClass(ro.getClassNames()[0]);
題外話:
為啥出現(xiàn)了這個(gè)錯(cuò)誤?
出現(xiàn)這個(gè)報(bào)錯(cuò)和開發(fā)的強(qiáng)轉(zhuǎn)類型也有關(guān)系,本地做了個(gè)小測試,同樣的數(shù)據(jù)。(但咱也沒發(fā)改開發(fā)的代碼,只能提提建議。 = =)
1、當(dāng)前異常轉(zhuǎn)化:按照開發(fā)業(yè)務(wù)代碼中的list強(qiáng)轉(zhuǎn)對象
List<Object> list = JSON.*parseArray*(jsonString); PassengerFlightInfo passengerFlightInfo = (PassengerFlightInfo) list.get(0);
這是使用強(qiáng)制類型轉(zhuǎn)換的方式,直接將 list 中的第一個(gè)元素強(qiáng)制轉(zhuǎn)換為 PassengerFlightInfo 對象。
這種方式在編譯時(shí)不會報(bào)錯(cuò),但如果 list 中的第一個(gè)元素不是 PassengerFlightInfo 對象,則會在運(yùn)行時(shí)拋出 ClassCastException 異常。
2、正常轉(zhuǎn)化:優(yōu)化過后用toJavaObject方法
PassengerFlightInfo passengerFlightInfo = ((JSONObject) list.get(0)).toJavaObject(PassengerFlightInfo.class);
這是使用 FastJSON 提供的 toJavaObject 方法,將 JSONObject 類型轉(zhuǎn)換為 PassengerFlightInfo 對象。
這種方式在運(yùn)行時(shí)會檢查轉(zhuǎn)換是否可行,如果 JSONObject 不包含 PassengerFlightInfo 的屬性或結(jié)構(gòu)不匹配,會拋出異常。這種方式更安全,因?yàn)樗峁┝烁嗟霓D(zhuǎn)換檢查。
推薦使用第二種方式,因?yàn)樗咏押桶踩軌蚋玫靥幚砜赡艹霈F(xiàn)的異常情況,并提供更好的錯(cuò)誤信息。
以上就是使用jvm sandbox對三層嵌套類型的改造示例的詳細(xì)內(nèi)容,更多關(guān)于jvm sandbox改造三層嵌套類型的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
java學(xué)生信息管理系統(tǒng)設(shè)計(jì)
這篇文章主要為大家詳細(xì)介紹了java學(xué)生信息管理系統(tǒng)設(shè)計(jì),學(xué)生信息添加進(jìn)入數(shù)據(jù)庫的事務(wù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11
Java調(diào)用Python代碼實(shí)現(xiàn)Word轉(zhuǎn)化為PDF格式
這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)調(diào)用Python代碼實(shí)現(xiàn)Word轉(zhuǎn)化為PDF格式,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2026-03-03
Java 詳解循環(huán)屏障CyclicBarrier如何實(shí)現(xiàn)多線程分段等待執(zhí)行完成
CyclicBarrier是一個(gè)同步工具類,可以翻譯成循環(huán)屏障,也叫障礙器或同步屏障。CyclicBarrier內(nèi)部有一個(gè)計(jì)數(shù)器count,調(diào)用障礙器的await方法會使計(jì)數(shù)器count的值減一,當(dāng)計(jì)數(shù)器count的值為0時(shí),表明調(diào)用了await方法線程已經(jīng)達(dá)到了設(shè)置的數(shù)量2021-11-11
SpringBoot+Ant Design Vue實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出功能方式
這篇文章主要介紹了SpringBoot+Ant Design Vue實(shí)現(xiàn)數(shù)據(jù)導(dǎo)出功能方式,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-01-01
基于SpringBoot和MongoDB實(shí)現(xiàn)實(shí)時(shí)分析和日志處理功能
實(shí)時(shí)分析和日志處理在現(xiàn)代應(yīng)用程序開發(fā)中扮演著重要的角色,MongoDB是一個(gè)非常流行的NoSQL數(shù)據(jù)庫,其高性能和靈活性使其成為實(shí)時(shí)分析和日志處理的理想選擇,本文將介紹如何使用?Spring?Boot?和?MongoDB?實(shí)現(xiàn)實(shí)時(shí)分析和日志處理的功能2023-06-06
解決springcloud啟動時(shí)報(bào)錯(cuò)Connection refused:connect問題
這篇文章主要介紹了解決springcloud啟動時(shí)報(bào)錯(cuò)Connection refused:connect問題,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05

