这几天打算自己写一个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() { publicT 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 extends T> 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动态代理,简单省事,性能又好,又非常的可靠稳定。