關(guān)于dubbo的validation問(wèn)題分析

一、背景

日常真線錯(cuò)誤日志排查中,日志經(jīng)常出現(xiàn),warn級(jí)別的錯(cuò)誤日志,如下 frozen class cannot edit,但是不影響業(yè)務(wù),太多的warn日志,影響真線的其他問(wèn)題排查,因此需要進(jìn)一步分析排查原因;


圖1

問(wèn)題:
為什么dubbo的consumer端沒(méi)有配置validation=true 默認(rèn)是false,會(huì)有校驗(yàn)器進(jìn)行參數(shù)校驗(yàn)?

二、源碼分析
出現(xiàn)該問(wèn)題是在應(yīng)用某列表頁(yè)或詳情頁(yè)調(diào)用了下面接口:


圖2

圖3

根據(jù)異常堆棧信息,進(jìn)行分析;(提前說(shuō)下:導(dǎo)致該問(wèn)題出現(xiàn)的原因與類加載器有關(guān))
step1、首次加載獲取validator,屬性中的clazz加載器是LaunchedURLClassLoader, 不是當(dāng)前線程的TomcatEmbeddedWebappClassLoader
com.alibaba.dubbo.validation.support.AbstractValidation#getValidator
這里clazz對(duì)應(yīng)的是xxx....DepositProjectExternalFacade,見(jiàn)下圖


圖4

step2、
com.alibaba.dubbo.validation.support.jvalidation.JValidator#JValidator
其中clazz屬性通過(guò)反射獲取
圖5

反射使用的是當(dāng)前線程上下文中的加載器TomcatEmbeddedWebappClassLoader


圖6
圖7

通過(guò)反射獲取
TomcatEmbeddedWebappClassLoader的父加載器是LaunchedURLClassLoader,根據(jù)雙親委派原則,clazz實(shí)際通過(guò)父加載器LaunchedURLClassLoader加載,如下圖


圖8

clazz對(duì)象
注:spring 內(nèi)嵌了tomcat,spring指定TomcatEmbeddedWebappClassLoader的父加載器為L(zhǎng)aunchedURLClassLoader
參見(jiàn):org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory #prepareContext


圖9
圖10

<font color=#FF7F00 >step3、</font> <font color=#FF00FF>上面的JValidator校驗(yàn)器初始化加載部分,下面進(jìn)入正文分析</font>
com.alibaba.dubbo.validation.support.jvalidation.JValidator#validate(java.lang.String, java.lang.Class<?>[], java.lang.Object[])

圖11

圖11
step4、接著進(jìn)入com.alibaba.dubbo.validation.support.jvalidation.JValidator#getMethodParameterBean
圖12

首先假設(shè)第一次調(diào)用xxx.....DepositProjectExternalFacade(應(yīng)用重啟以后第一次調(diào)用)
上圖中的1 parameterClass = (Class<?>) Class.forName(parameterClassName, true, clazz.getClassLoader());
parameterClassName = xxx......DepositProjectExternalFacadeQueryPaymentParameter ![圖13](https://upload-images.jianshu.io/upload_images/18232435-d0e436a499212da7?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 如上圖 clazz.getClassLoader()的加載器為L(zhǎng)aunchedURLClassLoader(step1中已經(jīng)分析過(guò)), 首次加載,當(dāng)前jvm中沒(méi)有parameterClassName這個(gè)class,因此通過(guò)反射獲取該class對(duì)象會(huì)報(bào)錯(cuò),提示classNotFoundException ![圖14](https://upload-images.jianshu.io/upload_images/18232435-621b6e1eaff4cc41?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 然后dubbo做了個(gè)try catch,如上圖的第2步,CtClass ctClass = pool.makeClass(parameterClassName); 對(duì)異常捕獲后利用字節(jié)碼javassist工具,動(dòng)態(tài)生成字節(jié)碼,相關(guān)知識(shí)點(diǎn)網(wǎng)上比較多,可以自行查找,限于篇幅本文不做擴(kuò)展 可以參考[參考文獻(xiàn)1](https://blog.csdn.net/a394268045/article/details/51996082)、[參考文獻(xiàn)2](https://www.cnblogs.com/sunfie/p/5154246.html)(這里會(huì)涉及到凍結(jié)frozen 和 解凍defrost的概念 本文的核心)上圖的第三步 parameterClass = ctClass.toClass();當(dāng)CtClass 調(diào)用writeFile()、toClass()、toBytecode() 這些方法的時(shí)候,Javassist會(huì)凍結(jié)CtClass Object,對(duì)CtClass object的修改將不允許。這個(gè)主要是為了警告開(kāi)發(fā)者該類已經(jīng)被加載,而JVM是不允許重新加載該類的。繼續(xù)看下ctClass.toClass() ![圖15](https://upload-images.jianshu.io/upload_images/18232435-b4c4c640c09bd49c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 一般是rest請(qǐng)求,當(dāng)前線程的上下文加載器是TomcatEmbeddedWebappClassLoader,因此第一次加載parameterClass對(duì)象的類加載器是TomcatEmbeddedWebappClassLoader; 然后。。。。。; ![圖16](https://upload-images.jianshu.io/upload_images/18232435-35872c676ed76872?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 獲取到parameterClass對(duì)象,然后返回,所有功能正常; step5、繼續(xù)第二次調(diào)用 此時(shí)如進(jìn)入到上圖,然后進(jìn)入圖中標(biāo)注1 parameterClass = (Class<?>) Class.forName(parameterClassName, true, clazz.getClassLoader()); 上文解釋過(guò)加載 parameterClass (DepositProjectExternalFacadeQueryPaymentParameter)
這個(gè)對(duì)象的加載器是TomcatEmbeddedWebappClassLoader,但是clazz.getClassLoader()這個(gè)是LaunchedURLClassLoader(父加載器),因此也會(huì)報(bào)錯(cuò),對(duì)象不是同一個(gè)加載器,也不符合雙親委派原則;
此時(shí)還會(huì)繼續(xù)走catch的邏輯,pool.makeClass(parameterClassName),此時(shí)就要涉及上文提到過(guò)的凍結(jié)了,ctclass對(duì)象在第一次調(diào)用的時(shí)候已經(jīng)new 出來(lái)了,
同時(shí)最后做了toclass,故此時(shí)ctclass已經(jīng)被凍結(jié);再第二次makeClass時(shí)會(huì)進(jìn)行是否被凍結(jié)的校驗(yàn),如果已經(jīng)凍結(jié),會(huì)直接報(bào)錯(cuò)
圖17

圖18

真線報(bào)錯(cuò)的信息,就是這里拋出的,至此定位到問(wèn)題所在;
step6、拋出錯(cuò)誤后對(duì)應(yīng)用會(huì)有什么影響嗎?
圖19

由圖20 可知這里在最外層做了try cache,直接返回null,


圖20

如果返回null,圖21中圈起來(lái)的邏輯就不走了,這個(gè)對(duì)應(yīng)的是圖5中的@Notnull注解不起作用,因此不影響業(yè)務(wù)功能;

三、問(wèn)題分析

3.1 我們來(lái)看下開(kāi)頭提出的問(wèn)題
consumer沒(méi)有注冊(cè)validation=true,為什么會(huì)在consumer端進(jìn)行校驗(yàn)我們來(lái)看web-reverse中,dubbo初始化加載的配置
com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers


圖21

com.alibaba.dubbo.registry.integration.RegistryDirectory#mergeUrl 如果consumer沒(méi)有配置會(huì)使用provide端的配置


圖22

如果consumer端配置了,會(huì)對(duì)dubbo-validation配置進(jìn)行覆蓋,如果沒(méi)有配置就繼承provider的值


圖23

因此我們可以看到,雖然反向競(jìng)價(jià)consumer中沒(méi)有配置validation=true,但是provide配置了這個(gè)validation=true,因此導(dǎo)致consumer進(jìn)行相關(guān)的校驗(yàn)


圖24

3.2 解決方法
在dubbo的consumer配置中,加上validation=false,關(guān)閉消費(fèi)端校驗(yàn)
注:最新版dubbo已經(jīng)解決該問(wèn)題,解決方案,和通過(guò)反射獲取的類加載保持一致,即當(dāng)前類的加載器
parameterClass = ctClass.toClass(clazz.getClassLoader(), (ProtectionDomain)null);

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容