最近在阅读dubbo发现涉及到的技术点太多了,整理了下大概涉及到的技术点有:
动态代理、注册中心、传输协议、编码和解码、序列化和反序列化、SPI机制、dubbo协议、线程池 等。

动态代理大家都不陌生,而且在工作中也经常用,rpc在消费远程服务的时候,为了让远程的服务调用像本地一样,就用到了动态代理,主要来解决传输协议、序列化、编码等问题,这些细节就可以叫给动态代理来实现。

常用的动态代理有

dubbo的动态代理默认使用的是javassist,所以好奇为什么选择javassist,而不是选择更易用的jdk或者cglib呢?因为javassist的编写难度实在是比较大的,而且代码不太好为何。
微博开源的motan用的就是jdk的动态代理的方式,那么如果javassist的性能真的像网上说的差距有10倍的话,如果是这样的话,为什么motan要选择jdk的方式呢?
所以今天我就来比较下各种动态代理的性能吧,现在市面上主流的动态代理主要有:
jdk动态代理
cglib动态代理
javassist动态代理
asm动态代理

下面主要对几种主流的代理框架的使用和性能进行分析下

jdk的动态代理的实现

private static CountService createJdkDynamicProxy(final CountService delegate) {
    CountService jdkProxy = (CountService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),
            new Class[]{CountService.class}, new JdkHandler(delegate));
    return jdkProxy;
}

private static class JdkHandler implements InvocationHandler {

    final Object delegate;

    JdkHandler(Object delegate) {
        this.delegate = delegate;
    }

    public Object invoke(Object object, Method method, Object[] objects)
            throws Throwable {
        return method.invoke(delegate, objects);
    }
}

cglib动态代理实现

private static CountService createCglibDynamicProxy(final CountService delegate) throws Exception {
    Enhancer enhancer = new Enhancer();
    enhancer.setCallback(new CglibInterceptor(delegate));
    enhancer.setInterfaces(new Class[]{CountService.class});
    CountService cglibProxy = (CountService) enhancer.create();
    return cglibProxy;
}

private static class CglibInterceptor implements MethodInterceptor {

    final Object delegate;

    CglibInterceptor(Object delegate) {
        this.delegate = delegate;
    }

    public Object intercept(Object object, Method method, Object[] objects,
                            MethodProxy methodProxy) throws Throwable {
        return methodProxy.invoke(delegate, objects);
    }
}

javassist提供的动态代理

private static CountService createJavassistDynamicProxy(final CountService delegate) throws Exception {
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.setInterfaces(new Class[]{CountService.class});
    Class<?> proxyClass = proxyFactory.createClass();
    CountService javassistProxy = (CountService) proxyClass.newInstance();
    ((ProxyObject) javassistProxy).setHandler(new JavaAssitInterceptor(delegate));
    return javassistProxy;
}

private static class JavaAssitInterceptor implements MethodHandler {

    final Object delegate;

    JavaAssitInterceptor(Object delegate) {
        this.delegate = delegate;
    }

    public Object invoke(Object self, Method m, Method proceed,
                         Object[] args) throws Throwable {
        return m.invoke(delegate, args);
    }
}

javassist字节码动态代理

private static CountService createJavassistBytecodeDynamicProxy(CountService delegate) throws Exception {
    ClassPool mPool = new ClassPool(true);
    CtClass mCtc = mPool.makeClass(CountService.class.getName() + "JavaassistProxy");
    mCtc.addInterface(mPool.get(CountService.class.getName()));
    mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
    mCtc.addField(CtField.make("public " + CountService.class.getName() + " delegate;", mCtc));
    mCtc.addMethod(CtNewMethod.make("public int count() { return delegate.count(); }", mCtc));
    Class<?> pc = mCtc.toClass();
    CountService bytecodeProxy = (CountService) pc.newInstance();
    Field filed = bytecodeProxy.getClass().getField("delegate");
    filed.set(bytecodeProxy, delegate);
    return bytecodeProxy;
}

asm动态代理

private static CountService createAsmBytecodeDynamicProxy(CountService delegate) throws Exception {
    ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
    String className = CountService.class.getName() + "AsmProxy";
    String classPath = className.replace('.', '/');
    String interfacePath = CountService.class.getName().replace('.', '/');
    classWriter.visit(Opcodes.V1_5, Opcodes.ACC_PUBLIC, classPath, null, "java/lang/Object", new String[]{interfacePath});

    MethodVisitor initVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
    initVisitor.visitCode();
    initVisitor.visitVarInsn(Opcodes.ALOAD, 0);
    initVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
    initVisitor.visitInsn(Opcodes.RETURN);
    initVisitor.visitMaxs(0, 0);
    initVisitor.visitEnd();

    FieldVisitor fieldVisitor = classWriter.visitField(Opcodes.ACC_PUBLIC, "delegate", "L" + interfacePath + ";", null, null);
    fieldVisitor.visitEnd();

    MethodVisitor methodVisitor = classWriter.visitMethod(Opcodes.ACC_PUBLIC, "count", "()I", null, null);
    methodVisitor.visitCode();
    methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
    methodVisitor.visitFieldInsn(Opcodes.GETFIELD, classPath, "delegate", "L" + interfacePath + ";");
    methodVisitor.visitMethodInsn(Opcodes.INVOKEINTERFACE, interfacePath, "count", "()I");
    methodVisitor.visitInsn(Opcodes.IRETURN);
    methodVisitor.visitMaxs(0, 0);
    methodVisitor.visitEnd();

    classWriter.visitEnd();
    byte[] code = classWriter.toByteArray();
    CountService bytecodeProxy = (CountService) new ByteArrayClassLoader().getClass(className, code).newInstance();
    Field filed = bytecodeProxy.getClass().getField("delegate");
    filed.set(bytecodeProxy, delegate);
    return bytecodeProxy;
}

private static class ByteArrayClassLoader extends ClassLoader {

    public ByteArrayClassLoader() {
        super(ByteArrayClassLoader.class.getClassLoader());
    }

    public synchronized Class<?> getClass(String name, byte[] code) {
        if (name == null) {
            throw new IllegalArgumentException("");
        }
        return defineClass(name, code, 0, code.length);
    }

}

版本及测试结果

java version "1.8.0_161"
javassist 3.21.0
cglib 3.2.5
asm 5.2

测试1千万次的测试结果是:
Create JDK Proxy: 11 ms
Create CGLIB Proxy: 115 ms
Create JAVAASSIST Proxy: 110 ms
Create JAVAASSIST Bytecode Proxy: 80 ms
Create ASM Proxy: 1 ms
================
Run JDK Proxy: 47 ms, 30,001,391 t/s
Run CGLIB Proxy: 107 ms, 13,178,181 t/s
Run JAVAASSIST Proxy: 201 ms, 7,015,250 t/s
Run JAVAASSIST Bytecode Proxy: 50 ms, 28,201,308 t/s
Run ASM Bytecode Proxy: 61 ms, 23,115,826 t/s


Run JDK Proxy: 81 ms, 17,408,214 t/s
Run CGLIB Proxy: 29 ms, 48,622,945 t/s
Run JAVAASSIST Proxy: 240 ms, 5,875,272 t/s
Run JAVAASSIST Bytecode Proxy: 28 ms, 50,359,478 t/s
Run ASM Bytecode Proxy: 60 ms, 23,501,090 t/s

Run JDK Proxy: 90 ms, 15,667,393 t/s
Run CGLIB Proxy: 94 ms, 15,000,695 t/s
Run JAVAASSIST Proxy: 354 ms, 3,983,235 t/s
Run JAVAASSIST Bytecode Proxy: 66 ms, 21,364,627 t/s
Run ASM Bytecode Proxy: 71 ms, 19,860,076 t/s

从易用性和可读性来说当然是jdk或者cglib更好,字节码的javassist当然是很优秀的一种选择,但是在jdk1.8以后jdk的动态代理也不差。

cglib是基于asm的。性能上的话javassist>cglib>jdk

但是,jdk 1.8已经对jdk的代理做了优化,所以梁飞的这个测试已经是过时的了。实际上cglib已经没有传言的比jdk的快10倍

动态代理dubbo中消费者服务中使用

dubbo有多处用到javassist字节码,比如我们之前讲的spi扩展的时候自适应扩展机制的Adaptive生成。
还有reference生成消费者refer的时候的代理,让调用者像调用本地服务一样调用远程:

JavassistProxyFactory是一个代理的工厂方法,继承抽象类AbstractProxyFactory

public class JavassistProxyFactory extends AbstractProxyFactory {

    @SuppressWarnings("unchecked")
    public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {
        return (T) Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));
    }

    public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {
        // TODO Wrapper类不能正确处理带$的类名
        final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);
        return new AbstractProxyInvoker<T>(proxy, type, url) {
            @Override
            protected Object doInvoke(T proxy, String methodName, 
                                      Class<?>[] parameterTypes, 
                                      Object[] arguments) throws Throwable {
                return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);
            }
        };
    }

}

下面代理类

public static Proxy getProxy(ClassLoader cl, Class<?>... ics)
	{
		if( ics.length > 65535 )
			throw new IllegalArgumentException("interface limit exceeded");
		
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<ics.length;i++)
		{
			String itf = ics[i].getName();
			if( !ics[i].isInterface() )
				throw new RuntimeException(itf + " is not a interface.");

			Class<?> tmp = null;
			try
			{
				tmp = Class.forName(itf, false, cl);
			}
			catch(ClassNotFoundException e)
			{}

			if( tmp != ics[i] )
				throw new IllegalArgumentException(ics[i] + " is not visible from class loader");

		    sb.append(itf).append(';');
		}

		// use interface class name list as key.
		String key = sb.toString();

		// get cache by class loader.
		Map<String, Object> cache;
		synchronized( ProxyCacheMap )
		{
			cache = ProxyCacheMap.get(cl);
			if( cache == null )
		    {
				cache = new HashMap<String, Object>();
				ProxyCacheMap.put(cl, cache);
		    }
		}

		Proxy proxy = null;
		synchronized( cache )
		{
			do
			{
				Object value = cache.get(key);
				if( value instanceof Reference<?> )
				{
					proxy = (Proxy)((Reference<?>)value).get();
					if( proxy != null )
						return proxy;
				}

				if( value == PendingGenerationMarker )
				{
					try{ cache.wait(); }catch(InterruptedException e){}
				}
				else
				{
					cache.put(key, PendingGenerationMarker);
					break;
				}
			}
			while( true );
		}

		long id = PROXY_CLASS_COUNTER.getAndIncrement();
		String pkg = null;
		ClassGenerator ccp = null, ccm = null;
		try
		{
			ccp = ClassGenerator.newInstance(cl);

			Set<String> worked = new HashSet<String>();
			List<Method> methods = new ArrayList<Method>();

			for(int i=0;i<ics.length;i++)
			{
				if( !Modifier.isPublic(ics[i].getModifiers()) )
				{
					String npkg = ics[i].getPackage().getName();
					if( pkg == null )
					{
						pkg = npkg;
					}
					else
					{
						if( !pkg.equals(npkg)  )
							throw new IllegalArgumentException("non-public interfaces from different packages");
					}
				}
				ccp.addInterface(ics[i]);

				for( Method method : ics[i].getMethods() )
				{
					String desc = ReflectUtils.getDesc(method);
					if( worked.contains(desc) )
						continue;
					worked.add(desc);

					int ix = methods.size();
					Class<?> rt = method.getReturnType();
					Class<?>[] pts = method.getParameterTypes();

					StringBuilder code = new StringBuilder("Object[] args = new Object[").append(pts.length).append("];");
					for(int j=0;j<pts.length;j++)
						code.append(" args[").append(j).append("] = ($w)$").append(j+1).append(";");
					code.append(" Object ret = handler.invoke(this, methods[" + ix + "], args);");
					if( !Void.TYPE.equals(rt) )
						code.append(" return ").append(asArgument(rt, "ret")).append(";");

					methods.add(method);
					ccp.addMethod(method.getName(), method.getModifiers(), rt, pts, method.getExceptionTypes(), code.toString());
				}
			}

			if( pkg == null )
				pkg = PACKAGE_NAME;

			// create ProxyInstance class.
			String pcn = pkg + ".proxy" + id;
			ccp.setClassName(pcn);
			ccp.addField("public static java.lang.reflect.Method[] methods;");
			ccp.addField("private " + InvocationHandler.class.getName() + " handler;");
			ccp.addConstructor(Modifier.PUBLIC, new Class<?>[]{ InvocationHandler.class }, new Class<?>[0], "handler=$1;");
            ccp.addDefaultConstructor();
			Class<?> clazz = ccp.toClass();
			clazz.getField("methods").set(null, methods.toArray(new Method[0]));

			// create Proxy class.
			String fcn = Proxy.class.getName() + id;
			ccm = ClassGenerator.newInstance(cl);
			ccm.setClassName(fcn);
			ccm.addDefaultConstructor();
			ccm.setSuperClass(Proxy.class);
			ccm.addMethod("public Object newInstance(" + InvocationHandler.class.getName() + " h){ return new " + pcn + "($1); }");
			Class<?> pc = ccm.toClass();
			proxy = (Proxy)pc.newInstance();
		}
		catch(RuntimeException e)
		{
			throw e;
		}
		catch(Exception e)
		{
			throw new RuntimeException(e.getMessage(), e);
		}
		finally
		{
			// release ClassGenerator
			if( ccp != null )
				ccp.release();
			if( ccm != null )
				ccm.release();
			synchronized( cache )
			{
				if( proxy == null )
					cache.remove(key);
				else
					cache.put(key, new WeakReference<Proxy>(proxy));
				cache.notifyAll();
			}
		}
		return proxy;
	}

由于篇幅有限,和本文主要回顾了几种常见的动态代理的实现方式,对于dubbo的reference的动态代理就不做更多的分析,原理和上面讲的javassist字节码动态代理是一样的。大家可以自行阅读相关源码。