深入分析rpc框架之动态代理
最近在阅读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字节码动态代理是一样的。大家可以自行阅读相关源码。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性