这几天打算自己写一个RPC框架,参考了阿里的dubbo,微博的motan等框架的设计。在选择代理技术时,dubbo有两种方案,一个是jdk的动态代理,一个JAVAASSIST的字节码生成技术,在dubbo作者梁飞的博客《动态代理方案性能对比》http://javatar.iteye.com/blog/814426中,因为作者在编写服务框架需要用动态代理生成客户端接口的stub,进行了几个动态代理方案性能的测试,测试表明了JAVAASSIST的字节码生成技术的性能应该是最好的。而微博的motan框架里,只使用了jdk代理技术。我脑海里就有疑问,为什么motan框架不用JAVAASSIST的字节码生成技术呢。所以我干脆把几种动态代理技术都用上,测试下在RPC框架中,几种动态代理的性能如何。
下面是测试用例:
maven依赖的包如下:
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>1.6.3</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>2.2</version>
</dependency>
<dependency>
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> T newProxyInstance(Class<T> inferfaceClass, Object handler) {
return inferfaceClass.cast(Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[] {inferfaceClass}, (InvocationHandler)handler));
}
}),
BYTE_BUDDY_PROXY(new ProxyFactory() {
public <T> T newProxyInstance(Class<T> 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> T newProxyInstance(Class<T> 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> T newProxyInstance(Class<T> inferfaceClass, Object handler) {
return (T) com.hcd.melody.proxy.bytecode.Proxy.getProxy(inferfaceClass).newInstance((InvocationHandler)handler);
}
}),
JAVASSIST_DYNAMIC_PROXY(new ProxyFactory() {
public <T> T newProxyInstance(Class<T> 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> T newProxyInstance(Class<T> interfaceType, Object handler) {
return factory.newProxyInstance(interfaceType, handler);
}
interface ProxyFactory{
<T> T newProxyInstance(Class<T> 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动态代理,简单省事,性能又好,又非常的可靠稳定。