Dubbo无论是生产端的暴露服务创建的invoker代理还是消费端创建的代理调用,都会需要经过代理。而Dubbo中默认采用javassit代理,动态的在内存当中生成所代理类的字节码,来完成代理的功能。

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) {@Overrideprotected Object doInvoke(T proxy, String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Throwable {return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);}};
}

可以看到,javassit的代理工厂类只实现了两个方法,恰恰是上文所说的消费端和生产端两个流程中的代理实现。以getProxy()为例子,也就是生成消费端的代理为例子。

getProxy()需要两个参数,一个是所需要被代理的类invoker,一个是代理类所实现的接口,而这些接口中所定义的方法,就是需要通过被代理达到远程调用生产端的作用。

首先,Dubbo通过getProxy()方法传入所要代理的接口所组成的数组。

public static Proxy getProxy(Class<?>... ics)
{return getProxy(ClassHelper.getCallerClassLoader(Proxy.class), ics);
}

但是这里可以看到,在生成代理类的字节码之前,仍需要通过得到当前Proxy的类加载器,防止类加载器之间的隔离。

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();

首先,会遍历传入的接口名,并确保传入的都确定是接口,将所有接口名拼接,用来获得所有接口名所组成的字符串,用来保证相同顺序的接口传入以生成字节码之后可以减轻生成代理的压力。

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 );
}

在Proxy中保存着static类型的map存放着每个类加载器所对应的加载了过的代理类。如果刚才所得到的key在这里能够相应的得到已经生成了的代理类,就不用再在下面生成代理,可以直接在map中取得并返回。

ccp = ClassGenerator.newInstance(cl);Set<String> worked = new HashSet<String>();
List<Method> methods = new ArrayList<Method>();

接下来,通过类加载器获得新的ClassGenerator,主要目的还是获得类加载器的对象池。

之后分别创建set来保存方法的描述符和list来保存所需要被代理的方法。

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());}
}

接下来遍历接口,通过获取接口的访问标志符,来判断是不是public方法,否则,将会判断该接口的包名是否与其他接口处于同一个包名下,如果不是,则会抛出异常。

public static boolean isPublic(int mod) {return (mod & PUBLIC) != 0;
}

接口访问标志符的判断通过位运算得到。

接下来,遍历接口下面的方法。,动态获得方法的描述符。

public static String getDesc(final Method m)
{StringBuilder ret = new StringBuilder(m.getName()).append('(');Class<?>[] parameterTypes = m.getParameterTypes();for(int i=0;i<parameterTypes.length;i++)ret.append(getDesc(parameterTypes[i]));ret.append(')').append(getDesc(m.getReturnType()));return ret.toString();
}

在这里,方法的描述符在这里解析。

例如 void do(int i)的方法,将在这里被解析为do(I)V。

在获得了方法描述符之后保存在set里面。

之后根据方法的参数返回值动态生成被代理的字节码,这里只生成方法体的字节码。

生成如下,假设被代理的方法返回值为boolean类型,参数数量为1,由于在java方法的调用中,会给第一个参数增加this,所以参数的配置从第二个开始:

Object[]args = new Object[1];

args[0]= 下标为1的参数;

Objectret = handler.invoke(this, methods[该方法在代理类中的下标], args);

returnret == null ? false : ((Boolean)ret).booleanValue();

之后会在ClassGenerator中的addMethod()方法加入代理类的字节码。

public ClassGenerator addMethod(String name, int mod, Class<?> rt, Class<?>[] pts, Class<?>[] ets, String body)
{StringBuilder sb = new StringBuilder();sb.append(modifier(mod)).append(' ').append(ReflectUtils.getName(rt)).append(' ').append(name);sb.append('(');for(int i=0;i<pts.length;i++){if( i > 0 )sb.append(',');sb.append(ReflectUtils.getName(pts[i]));sb.append(" arg").append(i);}sb.append(')');if( ets != null && ets.length > 0 ){sb.append(" throws ");for(int i=0;i<ets.length;i++){if( i > 0 )sb.append(',');sb.append(ReflectUtils.getName(ets[i]));}}sb.append('{').append(body).append('}');return addMethod(sb.toString());
}

这里会给方法加入方法的声明与结尾。这里就相对简单,新生成的方法的访问标志符,参数返回值都会直接写入,也会把方法所声明的异常抛出在这里增加在方法的声明上。在实现了方法的声明和方法体的拼接之后,将会把这字符串保存在ClassGenerator的方法数组里。

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();

在依次对所有的方法完成方法代理之后。将会给新的生成的类命名为报名.proxy + 生成的代理数量的id,这个类也是具体的代理的instance类。之后增加字段属性。

例如用来存放被调用的方法的methods,存放被代理对象的handler。

public static java.lang.reflect.Methods[]methods;

private InvocationHandler handler;

并且创建只有handler的构造方法,之后通过toClass()方法动态得到字节码编译后的class类。

在最后将之前的所有Method通过set保存在methods字段上。

之前的到的类作为Proxy的instance类,而还需要生成Proxy类来在之后的操作中可以通过不同参数来重复获得新的ProxyInstance。

关键要实现newInstance()方法,而这个方法在JavassistProxyFactory中马上将取得到,这个方法会根据传入的参数,通过新的类名的构造方法得到新的proxyInstance实例。
通过ClassGenerator的toClass()方法动态解析成可以使用的classe类,并最后通过newInstance()获得代理的实例。

CtClass ctcs = mSuperClass == null ? null : mPool.get(mSuperClass);
if( mClassName == null )mClassName = ( mSuperClass == null || javassist.Modifier.isPublic(ctcs.getModifiers())? ClassGenerator.class.getName() : mSuperClass + "$sc" ) + id;
mCtc = mPool.makeClass(mClassName);
if( mSuperClass != null )mCtc.setSuperclass(ctcs);
mCtc.addInterface(mPool.get(DC.class.getName())); // add dynamic class tag.
if( mInterfaces != null )for( String cl : mInterfaces ) mCtc.addInterface(mPool.get(cl));
if( mFields != null )for( String code : mFields ) mCtc.addField(CtField.make(code, mCtc));
if( mMethods != null )
{for( String code : mMethods ){if( code.charAt(0) == ':' )mCtc.addMethod(CtNewMethod.copy(getCtMethod(mCopyMethods.get(code.substring(1))), code.substring(1, code.indexOf('(')), mCtc, null));elsemCtc.addMethod(CtNewMethod.make(code, mCtc));}
}
if( mDefaultConstructor )mCtc.addConstructor(CtNewConstructor.defaultConstructor(mCtc));
if( mConstructors != null )
{for( String code : mConstructors ){if( code.charAt(0) == ':' ){mCtc.addConstructor(CtNewConstructor.copy(getCtConstructor(mCopyConstructors.get(code.substring(1))), mCtc, null));}else{String[] sn = mCtc.getSimpleName().split("\\$+"); // inner class name include $.mCtc.addConstructor(CtNewConstructor.make(code.replaceFirst(SIMPLE_NAME_TAG, sn[sn.length-1]), mCtc));}}
}
return mCtc.toClass(ClassHelper.getCallerClassLoader(getClass()), null);

在toClass()方法中,将之前得到的所有类配置得到,按照javassist的要求配置在javassist的api ctclass类当中,依次配置超类,接口,字段,方法,构造方法在ctClass当中        ,最后通过toClass()方法动态获得动态通过字节码生成的代理类。

把目光回到javassistProxyFactory,在的得到了新的Proxy之后,通过newInstance()方法得到了新的proxyInstance,而这个方法,也是在刚才动态生成的。这样既可通过javassist得到了新的代理对象。在消费者方法调用,实则会调用代理的相应的方法,而直接调用handler的方法,达到代理的目的。

Dubbo的Javassist代理相关推荐

  1. 【java】ASM代理方式 Byte-Buddy代理方式 Javassist代理方式

    1.概述 转载:添加链接描述 ASM代理方式 public class ASMProxy extends ClassLoader {public static <T> T getProxy ...

  2. dubbo中使用动态代理

    dubbo的动态代理也是只能代理接口 源码入口在JavassistProxyFactory中 public class JavassistProxyFactory extends AbstractPr ...

  3. 从原理到操作,让你在 APISIX 中代理 Dubbo 服务更便捷

    背景 Apache Dubbo 是由阿里巴巴开源并捐赠给 Apache 的微服务开发框架,它提供了 RPC 通信与微服务治理两大关键能力.不仅经过了阿里电商场景中海量流量的验证,也在国内的技术公司中被 ...

  4. dubbo源码分析(3)

    2019独角兽企业重金招聘Python工程师标准>>> 继上一章研究provider的加载过程之后,同理consumer的加载过程基本上和provider过程一模一样. 同样也是先读 ...

  5. Dubbo是如何进行远程服务调用的?(源码流程跟踪)

    首先会分析Dubbo是如何进行远程服务调用的,如果不了解dubbo的服务暴露和服务调用,请去看前两篇dubbo的文章,然后后面我还会说一下dubbo的SPI机制 当我们在使用@reference 注解 ...

  6. Dubbo服务暴露的流程

    在Dubbo服务暴露中,需要被暴露的服务的接口类首先会通过proxyFactory代理工厂获得代理的对象invoker,而暴露之后的服务被调用,也将都会通过这个invoker来调用相关的类. 在dub ...

  7. Dubbo核心知识点

    本文来说下Dubbo核心知识点,也是面试中的重难点. 文章目录 Dubbo是什么 RPC又是什么 说说Dubbo的分层 能说下Dubbo的工作原理吗 为什么要通过代理对象通信 说说服务暴露的流程 说说 ...

  8. dubbo整体设计整理

    dubbo整体组件设计.调用过程 API层 Service interface 业务层.包括业务代码的接口与实现,即开发者实现的业务代码 config ConfigParser配置层.主要围绕Serv ...

  9. dubbo源码分析7 之 服务本地暴露

    在上一篇文章我们分析了一下 dubbo 在服务暴露发生了哪些事,今天我们就来分析一下整个服务暴露中的本地暴露.(PS:其实我感觉本地暴露蛮鸡肋的).本地暴露需要服务提供方与服务消费方在同一个 JVM. ...

最新文章

  1. 表达式类型的实现数据结构_Redis系列(九)底层数据结构之五种基础数据类型的实现...
  2. CCF 2016年题目题解 - Python
  3. 什么是尾递归?测试python尾递归
  4. Java 基础 | 命名和运算
  5. php解析验证码,全面解析PHP验证码的实现原理 附php验证码小案例
  6. Code Pages Supported by Windows
  7. hssfrow 单元格样式_poi的各种单元格样式以及一些常用的配置
  8. MWC2010传递出什么信号?
  9. vue学习笔记—bootstrap+vue用户管理
  10. java 包的位置_通过Java在jar文件所在的位置创建目录
  11. 高斯数据库基于mysql_高斯数据库 (gaussDB) - 基于 JDBC 开发 (1)
  12. JAVA飞机移动斜着走_java复习 做一个简易的飞机游戏
  13. Java · 认识 String 类(上)· 创建字符串 · 字符串比较相等 · 字符串常量池 · 字符串不可变 · 字符字节与字符串
  14. php5.6 dbase,dBase数据库
  15. VSCode中MarkDown图片无法显示
  16. CSS 选择器优先级如何确定?
  17. Android 多种简单的弹出框样式设置
  18. 6、从键盘接收一百分制成绩(0~100),要求输出其对应的成绩等级A~E。其中,90分以上为‘A‘,80~89分为‘B‘,70~79分为‘C‘,60~69分为‘D‘,60分以下为‘E‘。
  19. 界面编程---用Java创建QQ登录页面
  20. Typora 设置图片自动上传

热门文章

  1. Android之创建选项菜单
  2. 安卓开发之如何利用Intent对象,实现Activity和另一个Activity之间的跳转
  3. 代码里配置java代理
  4. IoC、Spring 环境搭建、Spring 创建对象的三种方式、DI
  5. python安装扩展常用的工具是_Python 安装扩展库常用的是 _______ 工具_学小易找答案...
  6. python在windows上安装_在Windows上安装Python | 严佳冬
  7. Java中选择排序,冒泡排序,插入排序,快速排序
  8. 冒泡排序,选择排序,插入排序
  9. 一直对zookeeper的应用和原理比较迷糊,今天看一篇文章,讲得很通透,分享如下(转)...
  10. [20160606]windows下使用bbed的疑问.txt