关于dubbo的validation问题分析

一、背景

日常真线错误日志排查中,日志经常出现,warn级别的错误日志,如下 frozen class cannot edit,但是不影响业务,太多的warn日志,影响真线的其他问题排查,因此需要进一步分析排查原因;


图1

问题:
为什么dubbo的consumer端没有配置validation=true 默认是false,会有校验器进行参数校验?

二、源码分析
出现该问题是在应用某列表页或详情页调用了下面接口:


图2

图3

根据异常堆栈信息,进行分析;(提前说下:导致该问题出现的原因与类加载器有关)
step1、首次加载获取validator,属性中的clazz加载器是LaunchedURLClassLoader, 不是当前线程的TomcatEmbeddedWebappClassLoader
com.alibaba.dubbo.validation.support.AbstractValidation#getValidator
这里clazz对应的是xxx....DepositProjectExternalFacade,见下图


图4

step2、
com.alibaba.dubbo.validation.support.jvalidation.JValidator#JValidator
其中clazz属性通过反射获取
图5

反射使用的是当前线程上下文中的加载器TomcatEmbeddedWebappClassLoader


图6
图7

通过反射获取
TomcatEmbeddedWebappClassLoader的父加载器是LaunchedURLClassLoader,根据双亲委派原则,clazz实际通过父加载器LaunchedURLClassLoader加载,如下图


图8

clazz对象
注:spring 内嵌了tomcat,spring指定TomcatEmbeddedWebappClassLoader的父加载器为LaunchedURLClassLoader
参见:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory #prepareContext


图9
图10

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

图11

图11
step4、接着进入com.alibaba.dubbo.validation.support.jvalidation.JValidator#getMethodParameterBean
图12

首先假设第一次调用xxx.....DepositProjectExternalFacade(应用重启以后第一次调用)
上图中的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()的加载器为LaunchedURLClassLoader(step1中已经分析过), 首次加载,当前jvm中没有parameterClassName这个class,因此通过反射获取该class对象会报错,提示classNotFoundException ![图14](https://upload-images.jianshu.io/upload_images/18232435-621b6e1eaff4cc41?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 然后dubbo做了个try catch,如上图的第2步,CtClass ctClass = pool.makeClass(parameterClassName); 对异常捕获后利用字节码javassist工具,动态生成字节码,相关知识点网上比较多,可以自行查找,限于篇幅本文不做扩展 可以参考[参考文献1](https://blog.csdn.net/a394268045/article/details/51996082)、[参考文献2](https://www.cnblogs.com/sunfie/p/5154246.html)(这里会涉及到冻结frozen 和 解冻defrost的概念 本文的核心)上图的第三步 parameterClass = ctClass.toClass();当CtClass 调用writeFile()、toClass()、toBytecode() 这些方法的时候,Javassist会冻结CtClass Object,对CtClass object的修改将不允许。这个主要是为了警告开发者该类已经被加载,而JVM是不允许重新加载该类的。继续看下ctClass.toClass() ![图15](https://upload-images.jianshu.io/upload_images/18232435-b4c4c640c09bd49c?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 一般是rest请求,当前线程的上下文加载器是TomcatEmbeddedWebappClassLoader,因此第一次加载parameterClass对象的类加载器是TomcatEmbeddedWebappClassLoader; 然后。。。。。; ![图16](https://upload-images.jianshu.io/upload_images/18232435-35872c676ed76872?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 获取到parameterClass对象,然后返回,所有功能正常; step5、继续第二次调用 此时如进入到上图,然后进入图中标注1 parameterClass = (Class<?>) Class.forName(parameterClassName, true, clazz.getClassLoader()); 上文解释过加载 parameterClass (DepositProjectExternalFacadeQueryPaymentParameter)
这个对象的加载器是TomcatEmbeddedWebappClassLoader,但是clazz.getClassLoader()这个是LaunchedURLClassLoader(父加载器),因此也会报错,对象不是同一个加载器,也不符合双亲委派原则;
此时还会继续走catch的逻辑,pool.makeClass(parameterClassName),此时就要涉及上文提到过的冻结了,ctclass对象在第一次调用的时候已经new 出来了,
同时最后做了toclass,故此时ctclass已经被冻结;再第二次makeClass时会进行是否被冻结的校验,如果已经冻结,会直接报错
图17

图18

真线报错的信息,就是这里抛出的,至此定位到问题所在;
step6、抛出错误后对应用会有什么影响吗?
图19

由图20 可知这里在最外层做了try cache,直接返回null,


图20

如果返回null,图21中圈起来的逻辑就不走了,这个对应的是图5中的@Notnull注解不起作用,因此不影响业务功能;

三、问题分析

3.1 我们来看下开头提出的问题
consumer没有注册validation=true,为什么会在consumer端进行校验我们来看web-reverse中,dubbo初始化加载的配置
com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers


图21

com.alibaba.dubbo.registry.integration.RegistryDirectory#mergeUrl 如果consumer没有配置会使用provide端的配置


图22

如果consumer端配置了,会对dubbo-validation配置进行覆盖,如果没有配置就继承provider的值


图23

因此我们可以看到,虽然反向竞价consumer中没有配置validation=true,但是provide配置了这个validation=true,因此导致consumer进行相关的校验


图24

3.2 解决方法
在dubbo的consumer配置中,加上validation=false,关闭消费端校验
注:最新版dubbo已经解决该问题,解决方案,和通过反射获取的类加载保持一致,即当前类的加载器
parameterClass = ctClass.toClass(clazz.getClassLoader(), (ProtectionDomain)null);

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容