一、背景
日常真线错误日志排查中,日志经常出现,warn级别的错误日志,如下 frozen class cannot edit,但是不影响业务,太多的warn日志,影响真线的其他问题排查,因此需要进一步分析排查原因;
问题:
为什么dubbo的consumer端没有配置validation=true 默认是false,会有校验器进行参数校验?
二、源码分析
出现该问题是在应用某列表页或详情页调用了下面接口:
根据异常堆栈信息,进行分析;(提前说下:导致该问题出现的原因与类加载器有关)
step1、首次加载获取validator,属性中的clazz加载器是LaunchedURLClassLoader, 不是当前线程的TomcatEmbeddedWebappClassLoader
com.alibaba.dubbo.validation.support.AbstractValidation#getValidator
这里clazz对应的是xxx....DepositProjectExternalFacade,见下图
step2、
com.alibaba.dubbo.validation.support.jvalidation.JValidator#JValidator
其中clazz属性通过反射获取
反射使用的是当前线程上下文中的加载器TomcatEmbeddedWebappClassLoader
通过反射获取
TomcatEmbeddedWebappClassLoader的父加载器是LaunchedURLClassLoader,根据双亲委派原则,clazz实际通过父加载器LaunchedURLClassLoader加载,如下图
clazz对象
注:spring 内嵌了tomcat,spring指定TomcatEmbeddedWebappClassLoader的父加载器为LaunchedURLClassLoader
参见:org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory #prepareContext
<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
step4、接着进入com.alibaba.dubbo.validation.support.jvalidation.JValidator#getMethodParameterBean
首先假设第一次调用xxx.....DepositProjectExternalFacade(应用重启以后第一次调用)
上图中的1 parameterClass = (Class<?>) Class.forName(parameterClassName, true, clazz.getClassLoader());
parameterClassName = xxx......DepositProjectExternalFacadeQueryPaymentParameter)
这个对象的加载器是TomcatEmbeddedWebappClassLoader,但是clazz.getClassLoader()这个是LaunchedURLClassLoader(父加载器),因此也会报错,对象不是同一个加载器,也不符合双亲委派原则;
此时还会继续走catch的逻辑,pool.makeClass(parameterClassName),此时就要涉及上文提到过的冻结了,ctclass对象在第一次调用的时候已经new 出来了,
同时最后做了toclass,故此时ctclass已经被冻结;再第二次makeClass时会进行是否被冻结的校验,如果已经冻结,会直接报错
真线报错的信息,就是这里抛出的,至此定位到问题所在;
step6、抛出错误后对应用会有什么影响吗?
由图20 可知这里在最外层做了try cache,直接返回null,
如果返回null,图21中圈起来的逻辑就不走了,这个对应的是图5中的@Notnull注解不起作用,因此不影响业务功能;
三、问题分析
3.1 我们来看下开头提出的问题
consumer没有注册validation=true,为什么会在consumer端进行校验我们来看web-reverse中,dubbo初始化加载的配置
com.alibaba.dubbo.registry.integration.RegistryDirectory#toInvokers
com.alibaba.dubbo.registry.integration.RegistryDirectory#mergeUrl 如果consumer没有配置会使用provide端的配置
如果consumer端配置了,会对dubbo-validation配置进行覆盖,如果没有配置就继承provider的值
因此我们可以看到,虽然反向竞价consumer中没有配置validation=true,但是provide配置了这个validation=true,因此导致consumer进行相关的校验
3.2 解决方法
在dubbo的consumer配置中,加上validation=false,关闭消费端校验
注:最新版dubbo已经解决该问题,解决方案,和通过反射获取的类加载保持一致,即当前类的加载器
parameterClass = ctClass.toClass(clazz.getClassLoader(), (ProtectionDomain)null);