dubbo框架Validation校验问题

服务在调用外部接口时抛出异常,问题已经有人提出,新版本也已修复https://github.com/apache/incubator-dubbo/issues/712

原因分析

下面我来一步步分析该问题出现的原因

2019-04-18 10:45:37,095 WARN [Thread-12] com.alibaba.dubbo.validation.support.jvalidation.JValidator:getMethodParameterBean:158  [DUBBO] .......provider.RiderBlackListProvider_RiderBlackListParameter_java.lang.String class is frozen, dubbo version: 2.5.6.2-D-RELEASE, current host: 192.168.96.162
java.lang.RuntimeException: .......provider.RiderBlackListProvider_RiderBlackListParameter_java.lang.String class is frozen
	at javassist.CtClassType.checkModify(CtClassType.java:309) ~[javassist-3.21.0-GA.jar:?]
	at javassist.CtField.setModifiers(CtField.java:240) ~[javassist-3.21.0-GA.jar:?]
	at javassist.compiler.Javac.compileField(Javac.java:135) ~[javassist-3.21.0-GA.jar:?]
	at javassist.compiler.Javac.compile(Javac.java:93) ~[javassist-3.21.0-GA.jar:?]
	at javassist.CtField.make(CtField.java:162) ~[javassist-3.21.0-GA.jar:?]
	at com.alibaba.dubbo.validation.support.jvalidation.JValidator.getMethodParameterBean(JValidator.java:145) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.validation.support.jvalidation.JValidator.validate(JValidator.java:242) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.validation.filter.ValidationFilter.invoke(ValidationFilter.java:49) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:54) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at ....dubbo.filter.ConsumerTrackingFilter.invoke(ConsumerTrackingFilter.java:30) [dubbo-integration-0.0.2-20180420.072844-18.jar:0.0.2-SNAPSHOT]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:229) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:74) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.common.bytecode.proxy1.riderBlackList(proxy1.java) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.gallant.dispatch.RiderBlacklistFilterTest$1.run(RiderBlacklistFilterTest.java:35) [test-classes/:?]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
2019-04-18 10:45:37,097 WARN [Thread-13] com.alibaba.dubbo.validation.support.jvalidation.JValidator:getMethodParameterBean:158  [DUBBO] by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: ".../provider/RiderBlackListProvider_RiderBlackListParameter_java/lang/String", dubbo version: 2.5.6.2-D-RELEASE, current host: 192.168.96.162
javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: ".../provider/RiderBlackListProvider_RiderBlackListParameter_java/lang/String"
	at javassist.ClassPool.toClass(ClassPool.java:1170) ~[javassist-3.21.0-GA.jar:?]
	at javassist.ClassPool.toClass(ClassPool.java:1113) ~[javassist-3.21.0-GA.jar:?]
	at javassist.ClassPool.toClass(ClassPool.java:1071) ~[javassist-3.21.0-GA.jar:?]
	at javassist.CtClass.toClass(CtClass.java:1275) ~[javassist-3.21.0-GA.jar:?]
	at com.alibaba.dubbo.validation.support.jvalidation.JValidator.getMethodParameterBean(JValidator.java:149) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.validation.support.jvalidation.JValidator.validate(JValidator.java:242) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.validation.filter.ValidationFilter.invoke(ValidationFilter.java:49) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:54) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at ....dubbo.filter.ConsumerTrackingFilter.invoke(ConsumerTrackingFilter.java:30) [dubbo-integration-0.0.2-20180420.072844-18.jar:0.0.2-SNAPSHOT]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:229) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:74) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.common.bytecode.proxy1.riderBlackList(proxy1.java) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.gallant.dispatch.RiderBlacklistFilterTest$1.run(RiderBlacklistFilterTest.java:35) [test-classes/:?]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]
Caused by: java.lang.LinkageError: loader (instance of  sun/misc/Launcher$AppClassLoader): attempted  duplicate class definition for name: ".../provider/RiderBlackListProvider_RiderBlackListParameter_java/lang/String"
	at java.lang.ClassLoader.defineClass1(Native Method) ~[?:1.8.0_131]
	at java.lang.ClassLoader.defineClass(ClassLoader.java:763) ~[?:1.8.0_131]
	at java.lang.ClassLoader.defineClass(ClassLoader.java:642) ~[?:1.8.0_131]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_131]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_131]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_131]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_131]
	at javassist.ClassPool.toClass2(ClassPool.java:1183) ~[javassist-3.21.0-GA.jar:?]
	at javassist.ClassPool.toClass(ClassPool.java:1164) ~[javassist-3.21.0-GA.jar:?]
	... 24 more
2019-04-18 10:45:37,090 WARN [Thread-14] com.alibaba.dubbo.validation.support.jvalidation.JValidator:getMethodParameterBean:158  [DUBBO] cannot add, dubbo version: 2.5.6.2-D-RELEASE, current host: 192.168.96.162
javassist.CannotCompileException: cannot add
	at javassist.CtClassType.addField(CtClassType.java:1310) ~[javassist-3.21.0-GA.jar:?]
	at javassist.CtClass.addField(CtClass.java:1100) ~[javassist-3.21.0-GA.jar:?]
	at com.alibaba.dubbo.validation.support.jvalidation.JValidator.getMethodParameterBean(JValidator.java:147) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.validation.support.jvalidation.JValidator.validate(JValidator.java:242) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.validation.filter.ValidationFilter.invoke(ValidationFilter.java:49) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.monitor.support.MonitorFilter.invoke(MonitorFilter.java:75) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.dubbo.filter.FutureFilter.invoke(FutureFilter.java:54) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.filter.ConsumerContextFilter.invoke(ConsumerContextFilter.java:48) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at ....dubbo.filter.ConsumerTrackingFilter.invoke(ConsumerTrackingFilter.java:30) [dubbo-integration-0.0.2-20180420.072844-18.jar:0.0.2-SNAPSHOT]
	at com.alibaba.dubbo.rpc.protocol.ProtocolFilterWrapper$1.invoke(ProtocolFilterWrapper.java:69) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.listener.ListenerInvokerWrapper.invoke(ListenerInvokerWrapper.java:74) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.protocol.InvokerWrapper.invoke(InvokerWrapper.java:53) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.FailoverClusterInvoker.doInvoke(FailoverClusterInvoker.java:77) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.AbstractClusterInvoker.invoke(AbstractClusterInvoker.java:229) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.cluster.support.wrapper.MockClusterInvoker.invoke(MockClusterInvoker.java:72) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.rpc.proxy.InvokerInvocationHandler.invoke(InvokerInvocationHandler.java:74) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.alibaba.dubbo.common.bytecode.proxy1.riderBlackList(proxy1.java) [dubbo-2.5.6.2-D-RELEASE.jar:2.5.6.2-D-RELEASE]
	at com.gallant.dispatch.RiderBlacklistFilterTest$1.run(RiderBlacklistFilterTest.java:35) [test-classes/:?]
	at java.lang.Thread.run(Thread.java:748) [?:1.8.0_131]

老规矩,跟着堆栈看源码
查看javassist的ClassPool源码,frozen问题正是该处抛出,那么getCache获取到之后发现类处于冻结状态,所以抛出了异常

private static Object getMethodParameterBean(Class<?> clazz, Method method, Object[] args) {
	...
    try {
		//获取class名称
        String parameterClassName = generateMethodParameterClassName(clazz, method);
        Class<?> parameterClass;
        try {
		//根据名称获取class,如果获取失败则创建并缓存
            parameterClass = (Class<?>) Class.forName(parameterClassName, true, clazz.getClassLoader());
        } catch (ClassNotFoundException e) {
            ClassPool pool = ClassGenerator.getClassPool(clazz.getClassLoader());
            CtClass ctClass = pool.makeClass(parameterClassName);
            ...创建,拼接class内容
			//创建class字节码成功后并冻结
            parameterClass = ctClass.toClass();
        }
        ...
}
//创建class
public synchronized CtClass makeClass(String classname, CtClass superclass)
    throws RuntimeException
{
    checkNotFrozen(classname);
    CtClass clazz = new CtNewClass(classname, this, false, superclass);
    cacheCtClass(classname, clazz, true);
    return clazz;
}
//如果已冻结则抛出异常
void checkNotFrozen(String classname) throws RuntimeException {
    CtClass clazz = getCached(classname);
    if (clazz == null) {
        if (!childFirstLookup && parent != null) {
            try {
                clazz = parent.get0(classname, true);
            }
            catch (NotFoundException e) {}
            if (clazz != null)
                throw new RuntimeException(classname
                        + " is in a parent ClassPool.  Use the parent.");
        }
    }
    else
        if (clazz.isFrozen())
            throw new RuntimeException(classname
                                    + ": frozen class (cannot edit)");
}

创建CtNewClass对象的wasFrozen状态是false状态,缓存class文件,在文件创建的时候如果再次发起请求进入校验模块,那么从缓存中获取的class对象校验其状态便一定会是冻结状态。当然,前提是反射获取类失败的情况

为什么会出现两次都获取不到class呢?创建之后没有加载至对应的classloader?

我们看到反射获取类时使用的是接口class的classloader。创建类是使用的ctClass.toClass(),其实就是当前线程的classloader。
那么如果当前线程的classloader与接口的classloader不匹配那么就会出现ClassNotFoundException异常导致再次重复创建。所以就出现了我们看到的异常。

什么情况下会出现classloader不匹配呢?

  1. 首先我们先看下接口的classloader,也就是JValidator的clazz的classloader,可以看到其实就是ClassHelper.getClassLoader();里面其实就是当前线程上下文的classloader
public static ClassLoader getClassLoader(Class<?> cls) {
    ClassLoader cl = null;
    try {
        cl = Thread.currentThread().getContextClassLoader();
    } catch (Throwable ex) {
        // Cannot access thread context ClassLoader - falling back to system class loader...
    }
    if (cl == null) {
        // No thread context class loader -> use class loader of this class.
        cl = cls.getClassLoader();
    }
    return cl;
}
  1. 再看ClassPool加载ctClass的classloader,也是当前线程上下文的classloader
public Class toClass(CtClass clazz) throws CannotCompileException {
    // Some subclasses of ClassPool may override toClass(CtClass,ClassLoader).
    // So we should call that method instead of toClass(.., ProtectionDomain).
    return toClass(clazz, getClassLoader()); 
}
public ClassLoader getClassLoader() {
    return getContextClassLoader();
}
static ClassLoader getContextClassLoader() {
    return Thread.currentThread().getContextClassLoader();
}
  1. 如果两处获取当前线程上下文classloader存在并发问题导致不匹配就可能出现类似问题。

问题复现

  1. 那么来验证场景,模拟高并发场景进行校验,果然复现了问题(不是必现),也就验证了我们的分析
  2. 从新版本修复改问题的方式也验证了我们的分析,即使用clazz的classloader进行加载接口代理类
/**
 * 问题复现
 * @author 会灰翔的灰机
 * @date 2019/4/18
 */
public class RiderBlacklistFilterTest extends UnitTestBase {
	@Resource
	private RiderBlackListProvider riderBlackListProvider;

	@Test
	public void riderBlackList(){
		int size = 30;
		CyclicBarrier cb = new CyclicBarrier(size);
		for (int i=0;i<size;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					try {
						cb.await();
					} catch (InterruptedException | BrokenBarrierException e) {
						e.printStackTrace();
					}
					System.out.println(this + ":" + System.currentTimeMillis());
					riderBlackListProvider.riderBlackList("123");
				}
			}).start();
		}
	}
}

问题思考

  1. 那么两个都是当前线程上下文的classloader为什么不匹配呢?
  2. 那么有可能是存在并发的场景设置当前线程上下文classloader,于是我们查看了该方法的调用场景果然有很多地方使用,那么下一步只需要分析是否存在并发场景设置classloader产生冲突的问题即可dubbo框架Validation校验问题_第1张图片

你可能感兴趣的:(java)