动态代理在RPC框架中的性能对比

阅读更多

       这几天打算自己写一个RPC框架,参考了阿里的dubbo,微博的motan等框架的设计。在选择代理技术时,dubbo有两种方案,一个是jdk的动态代理,一个JAVAASSIST的字节码生成技术,在dubbo作者梁飞的博客《动态代理方案性能对比》http://javatar.iteye.com/blog/814426中,因为作者在编写服务框架需要用动态代理生成客户端接口的stub,进行了几个动态代理方案性能的测试,测试表明了JAVAASSIST的字节码生成技术的性能应该是最好的。而微博的motan框架里,只使用了jdk代理技术。我脑海里就有疑问,为什么motan框架不用JAVAASSIST的字节码生成技术呢。所以我干脆把几种动态代理技术都用上,测试下在RPC框架中,几种动态代理的性能如何。

      下面是测试用例:

      maven依赖的包如下:


            org.javassist
            javassist
            3.20.0-GA
        
        
            net.bytebuddy
            byte-buddy
            1.6.3
        
        
            cglib
            cglib-nodep
            2.2
        
        

  ProxyEnum:其中javassist字节码生成技术由于需要拼接字符串,比较复杂,所以我把dubbo中这部分的源码扒出来直接拿来用了。

 

 

package com.hcd.melody.proxy;

import com.hcd.melody.common.util.ReflectUtil;
import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyObject;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * 代理技术枚举类
 * Created by cd_huang on 2017/6/3.
 */
public enum ProxyEnum {
	JDK_PROXY(new ProxyFactory() {
		public  T newProxyInstance(Class inferfaceClass, Object handler) {
			return inferfaceClass.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {inferfaceClass}, (InvocationHandler)handler));
		}
	}),
	BYTE_BUDDY_PROXY(new ProxyFactory() {
		public  T newProxyInstance(Class inferfaceClass, Object handler) {
			Class cls = new ByteBuddy()
					.subclass(inferfaceClass)
					.method(ElementMatchers.isDeclaredBy(inferfaceClass))
					.intercept(MethodDelegation.to(handler, "handler"))
					.make()
					.load(inferfaceClass.getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
					.getLoaded();

			return ReflectUtil.newInstance(cls);
		}
	}),
	CGLIB_PROXY(new ProxyFactory() {
		public  T newProxyInstance(Class inferfaceClass, Object handler) {
			Enhancer enhancer = new Enhancer();
			enhancer.setCallback((MethodInterceptor)handler);
			enhancer.setInterfaces(new Class[] {inferfaceClass});
			return (T) enhancer.create();
		}
	}),
	JAVASSIST_BYTECODE_PROXY(new ProxyFactory() {
		public  T newProxyInstance(Class inferfaceClass, Object handler) {
			return (T) com.hcd.melody.proxy.bytecode.Proxy.getProxy(inferfaceClass).newInstance((InvocationHandler)handler);
		}
	}),
	JAVASSIST_DYNAMIC_PROXY(new ProxyFactory() {
		public  T newProxyInstance(Class inferfaceClass, Object handler) {
			javassist.util.proxy.ProxyFactory proxyFactory = new javassist.util.proxy.ProxyFactory();
			proxyFactory.setInterfaces(new Class[] { inferfaceClass });
			Class proxyClass = proxyFactory.createClass();
			T javassistProxy = (T)ReflectUtil.newInstance(proxyClass);
			((ProxyObject) javassistProxy).setHandler((MethodHandler)handler);
			return javassistProxy;
		}
	});
	private ProxyFactory factory;
	ProxyEnum(ProxyFactory factory){
         this.factory =factory;
	}

	public  T newProxyInstance(Class interfaceType, Object handler) {
		return factory.newProxyInstance(interfaceType, handler);
	}
	interface ProxyFactory{
		 T newProxyInstance(Class inferfaceClass, Object handler);
	}
}

 InvokeHandler:

 

 

package com.hcd.melody.proxy;

import javassist.util.proxy.MethodHandler;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 客户端代理类的处理
 * Created by cd_huang on 2017/6/3.
 */
public class InvokeHandler implements InvocationHandler, MethodHandler, MethodInterceptor {

	private Object doInvoke(Object proxy, Method method, Object[] args) {
		if (Object.class == method.getDeclaringClass()) {
			String name = method.getName();
			if ("equals".equals(name)) {
				return proxy == args[0];
			} else if ("hashCode".equals(name)) {
				return System.identityHashCode(proxy);
			} else if ("toString".equals(name)) {
				return proxy.getClass().getName() + "@" +
						Integer.toHexString(System.identityHashCode(proxy)) +
						", with InvokeHandler " + this;
			} else {
				throw new IllegalStateException(String.valueOf(method));
			}
		}
		//TODO RPC调用
		return null;
	}

	/**
	 * jdk invoke
	 *
	 * @param proxy
	 * @param method
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		return doInvoke(proxy, method, args);
	}

	/**
	 * byteBuddy invoke
	 *
	 * @param proxy
	 * @param method
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	@RuntimeType
	public Object byteBuddyInvoke(@This Object proxy, @Origin Method method, @AllArguments @RuntimeType Object[] args) throws Throwable {
		return doInvoke(proxy, method, args);
	}

	/**
	 * javassist invoke
	 *
	 * @param proxy
	 * @param method
	 * @param proceed
	 * @param args
	 * @return
	 * @throws Throwable
	 */
	public Object invoke(Object proxy, Method method, Method proceed, Object[] args) throws Throwable {
		return doInvoke(proxy, method, args);
	}

	/**
	 * cglib invoke
	 *
	 * @param proxy
	 * @param method
	 * @param args
	 * @param methodProxy
	 * @return
	 * @throws Throwable
	 */
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		return doInvoke(proxy, method, args);
	}
} 

TestService: 

package com.hcd.melody.test;

/**
 * test接口类
 * Created by cd_huang on 2017/6/3.
 */
public interface TestService {

	String test(String s);
}

TestProxy:

package com.hcd.melody.test;

import com.hcd.melody.proxy.*;
/**
 * 测试动态代理技术区别
 * Created by cd_huang on 2017/6/3.
 */
public class TestProxy {
	public static void main(String args[]) {
		InvokeHandler handler =new InvokeHandler();
		long time = System.currentTimeMillis();
		TestService test1 =ProxyEnum.JDK_PROXY.newProxyInstance(TestService.class,handler);
		time = System.currentTimeMillis() - time;
		System.out.println("Create JDK Proxy: " + time + " ms");
		time = System.currentTimeMillis();
		TestService test2 =ProxyEnum.BYTE_BUDDY_PROXY.newProxyInstance(TestService.class,handler);
		time = System.currentTimeMillis() - time;
		System.out.println("Create byteBuddy Proxy: " + time + " ms");
		time = System.currentTimeMillis();
		TestService test3 =ProxyEnum.CGLIB_PROXY.newProxyInstance(TestService.class,handler);
		time = System.currentTimeMillis() - time;
		System.out.println("Create CGLIB Proxy: " + time + " ms");
		time = System.currentTimeMillis();
		TestService test4 =ProxyEnum.JAVASSIST_BYTECODE_PROXY.newProxyInstance(TestService.class,handler);
		time = System.currentTimeMillis() - time;
		System.out.println("Create JAVASSIST Bytecode Proxy: " + time + " ms");
		time = System.currentTimeMillis();
		TestService test5 =ProxyEnum.JAVASSIST_DYNAMIC_PROXY.newProxyInstance(TestService.class,handler);
		time = System.currentTimeMillis() - time;
		System.out.println("Create JAVASSIST Proxy: " + time + " ms");
		String s ="proxy";
		System.out.println("----------------");
		for (int i = 0; i <10; i++) {
			test(test1, "Run JDK Proxy: ",s);
			test(test2, "Run byteBuddy Proxy: ",s);
			test(test3, "Run CGLIB Proxy: ",s);
			test(test4, "Run JAVASSIST Bytecode Proxy: ",s);
			test(test5, "Run JAVASSIST Proxy: ",s);
			System.out.println("----------------");
		}

	}
	private static void test(TestService service, String label,String s) {
		service.test(s); // warm up
		int count = 100000000;
		long time = System.currentTimeMillis();
		for (int i = 0; i < count; i++) {
			service.test(s);
		}
		time = System.currentTimeMillis() - time;
		System.out.println(label + time + " ms, ");
	}
}

 

测试结果如下:

Create JDK Proxy: 26 ms
Create byteBuddy Proxy: 639 ms
Create CGLIB Proxy: 103 ms
Create JAVASSIST Bytecode Proxy: 164 ms
Create JAVASSIST Proxy: 17 ms
----------------
Run JDK Proxy: 21 ms, 
Run byteBuddy Proxy: 241 ms, 
Run CGLIB Proxy: 951 ms, 
Run JAVASSIST Bytecode Proxy: 918 ms, 
Run JAVASSIST Proxy: 827 ms, 
----------------
Run JDK Proxy: 499 ms, 
Run byteBuddy Proxy: 632 ms, 
Run CGLIB Proxy: 547 ms, 
Run JAVASSIST Bytecode Proxy: 602 ms, 
Run JAVASSIST Proxy: 718 ms, 
----------------
Run JDK Proxy: 544 ms, 
Run byteBuddy Proxy: 567 ms, 
Run CGLIB Proxy: 563 ms, 
Run JAVASSIST Bytecode Proxy: 586 ms, 
Run JAVASSIST Proxy: 724 ms, 
----------------
Run JDK Proxy: 550 ms, 
Run byteBuddy Proxy: 553 ms, 
Run CGLIB Proxy: 555 ms, 
Run JAVASSIST Bytecode Proxy: 585 ms, 
Run JAVASSIST Proxy: 761 ms, 
----------------
Run JDK Proxy: 532 ms, 
Run byteBuddy Proxy: 552 ms, 
Run CGLIB Proxy: 528 ms, 
Run JAVASSIST Bytecode Proxy: 601 ms, 
Run JAVASSIST Proxy: 724 ms, 
----------------
Run JDK Proxy: 545 ms, 
Run byteBuddy Proxy: 518 ms, 
Run CGLIB Proxy: 518 ms, 
Run JAVASSIST Bytecode Proxy: 603 ms, 
Run JAVASSIST Proxy: 715 ms, 
----------------
Run JDK Proxy: 544 ms, 
Run byteBuddy Proxy: 544 ms, 
Run CGLIB Proxy: 544 ms, 
Run JAVASSIST Bytecode Proxy: 571 ms, 
Run JAVASSIST Proxy: 727 ms, 
----------------
Run JDK Proxy: 546 ms, 
Run byteBuddy Proxy: 589 ms, 
Run CGLIB Proxy: 536 ms, 
Run JAVASSIST Bytecode Proxy: 577 ms, 
Run JAVASSIST Proxy: 729 ms, 
----------------
Run JDK Proxy: 667 ms, 
Run byteBuddy Proxy: 632 ms, 
Run CGLIB Proxy: 508 ms, 
Run JAVASSIST Bytecode Proxy: 596 ms, 
Run JAVASSIST Proxy: 708 ms, 
----------------
Run JDK Proxy: 541 ms, 
Run byteBuddy Proxy: 532 ms, 
Run CGLIB Proxy: 533 ms, 
Run JAVASSIST Bytecode Proxy: 613 ms, 
Run JAVASSIST Proxy: 761 ms, 
----------------

  从上面的测试,我得出来的结论和梁飞完全不同,几种动态代理技术的性能差异不大,JDK Proxy、byteBuddy Proxy、CGLIB Proxy的性能会比JAVAASSIST Bytecode Proxy、JAVAASSIST Proxy稍微快一些,而梁飞的结论是javaassist字节码生成技术的性能是最好的。但是上面看来,javaassist字节码生成技术的性能是比较差的。为什么会有这个区别呢。是因为在梁飞的例子里,会对目标对象delegate进行调用。而jdk动态代理,cglib动态代理,javaassist动态代理,都会使用反射调用目标对象,而javaassist字节码生成技术,asm字节码生成技术,则没有使用反射调用目标对象,而是类似装饰器的设计模式,直接调用delegate.method(args)。所以,性能的差异在于用反射调用方法和直接调用。而在RPC框架或分布式服务框架中,并不需要用反射去调用某个类,而是拿到接口名,方法,以及参数,把这些信息发送给服务端让服务端去反射调用类,再把结果返回到客户端。所以,在我看来,dubbo框架的客户端中使用javaassist字节码生成技术完全没有必要,字节码生成技术的优势在于避免反射调用方法,而RPC框架的客户端则没有这样的场景。像微博的motan框架那样直接jdk动态代理,简单省事,性能又好,又非常的可靠稳定。

你可能感兴趣的:(动态代理,性能对比,rpc,jdk代理)