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

当我们在使用@reference 注解的时候,来调用我们的提供者的Service对象的时候,Dubbo中的服务调用是怎么实现的

Dubbo的远程服务调用

(1)首选Dubbo是通过Poxy对象来生成一个代理对象的

  1. 具体实现是在ReferenceConfig对象中调用的private T createProxy(Map<String, String> map)方法的,这个方法中有三种生成Invoker对象的方式,第一种是通过本地JVM,第二种是通过URL对象是不是为空判断进行判断,然后如果为空就从注册中心获取这个Invoker对象,否则就是从ReferenceConfig中的URL中拿到
  2. 上面那个方法中还会通过获取到的Invoker这里的【生成Invoker的过程后面补充】的对象去通过ProxyFactory生成Poxy对象,代码为:return proxyFactory.getProxy(this.invoker);,这里proxyFactory其实就是
 //ProxyFactory接口的javassist扩展类JavassistProxyFactory的getProxy方法实现public <T> T getProxy(Invoker<T> invoker, Class<?>[] interfaces) {return Proxy.getProxy(interfaces).newInstance(new InvokerInvocationHandler(invoker));}
  1. 然后通过第2步的getPoxy()方法去动态代理生成代理Poxy对象
    public static Proxy getProxy(Class<?>... ics) {return getProxy(ClassHelper.getClassLoader(Proxy.class), ics);}/*** Get proxy.** @param cl  class loader.* @param ics interface class array. 可以实现多个接口* @return Proxy instance.*/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.// 用接口类名做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);}//类名称后自动加序列号 0,1,2,3...long id = PROXY_CLASS_COUNTER.getAndIncrement();String pkg = null;//ClassGenerator dubbo用javassist实现的工具类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");}}//代理类添加要实现的接口Class对象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();//通过反射,把method数组放入,静态变量methods中,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();//设置父类为抽象类,Proxy类子类,ccm.setSuperClass(Proxy.class);//生成实现它的抽象方法newInstance代码//new 的实例对象,是上面生成的代理类 pcnccm.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 ClassGeneratorif (ccp != null)ccp.release();if (ccm != null)ccm.release();synchronized (cache) {if (proxy == null)cache.remove(key);else//放入缓存,key:实现的接口名,value 代理对象,这个用弱引用,//当jvm gc时,会打断对实例对象的引用,对象接下来就等待被回收。cache.put(key, new WeakReference<Proxy>(proxy));cache.notifyAll();}}return proxy;}

(2)这里是进行补充上面的那个【Invoker的怎么生成的步骤】,看一下Invoker中都包含了什么信息这么重要,这里需要强调一下这个Invoker生成的过程和Dubbo服务的暴露和导出生成的Invoker不太一样

  1. invoker对象是通过 InvokerInvocationHandler构造方法传入,而InvokerInvocationHandler对象是由JavassistProxyFactory类getProxy(Invoker invoker, Class<?>[] interfaces)方法创建。
    这还要回到调用proxyFactory.getProxy(invoker);方法的地方,即ReferenceConfig类的createProxy(Map<String, String> map)方法中
  2. 所以这个Invoker其实是通过ReferenceConfig 中的createProxy(Map<String, String> map)方法来生成的Invoker对象,这个就是下面中使用到的对象refprotocol ,private static final Protocol refprotocol = (Protocol)ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
 if (urls.size() == 1) {//只有一个直连地址或一个注册中心配置地址//这里的urls.get(0)协议,可能是直连地址(默认dubbo协议),也可能是regiter注册地址(zookeeper协议)//我们这里走的是注册中心,所以invoker = refprotocol.refer(interfaceClass, urls.get(0));//本例通过配置一个注册中心的形式(***看这里***)} else {//多个直连地址或者多个注册中心地址,甚至是两者的组合。List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();URL registryURL = null;for (URL url : urls) {//创建invoker放入invokersinvokers.add(refprotocol.refer(interfaceClass, url));if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {registryURL = url; // 多个注册中心,用最后一个registry url}}if (registryURL != null) { //有注册中心协议的URL,//对多个url,其中存在有注册中心的,写死用AvailableCluster集群策略//这其中包括直连和注册中心混合或者都是注册中心两种情况URL u = registryURL.addParameter(Constants.CLUSTER_KEY, AvailableCluster.NAME);invoker = cluster.join(new StaticDirectory(u, invokers));} else { // 多个直连的urlinvoker = cluster.join(new StaticDirectory(invokers));}}
  1. 上面的代码可以看出生成Invoker的有三种方式,

    • 第一种是refprotocol.refer(this.interfaceClass, (URL),这里的接口用的是SPI机制的dubbo对应下面的那个@SPI注解=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol这一句,所以这一种主要是使用的是Dubbo直连的协议都会走这一层
    • 第二种是 cluster.join(new StaticDirectory(u, invokers));,这个是加了路由的负载均衡相关的,这一部分主要都是路由层的东西
    • 第三种是 this.invoker = cluster.join(new StaticDirectory(invokers));也是路由层的东西, 路由层:封装多个提供者的路由及负载均衡,并桥接注册中心,以 Invoker 为中心,扩展接口为 Cluster, Directory, Router, LoadBalance
@SPI("dubbo")
public interface Protocol {int getDefaultPort();@Adaptive<T> Exporter<T> export(Invoker<T> var1) throws RpcException;@Adaptive<T> Invoker<T> refer(Class<T> var1, URL var2) throws RpcException;void destroy();
}

因为Dubbo中在实现远程调用的时候其实是通过Poxy对象生成的Invoker对象,那么就先看一下Invoker的怎么生成的,里面都包含了什么信息?

  • 这里的invoker对象,通过InvokerInvocationHandler构造方法传入,而InvokerInvocationHandler对象是由JavassistProxyFactory类
    getProxy(Invoker invoker, Class<?>[] interfaces)方法创建

Dubbo中的SPI机制的使用和分析

参考文章:https://cloud.tencent.com/developer/article/1109459

Dubbo是如何进行远程服务调用的?(源码流程跟踪)相关推荐

  1. Dubbo负载均衡的源码流程(2022.5.30)

    Dubbo负载均衡的源码流程 1.默认负载均衡策略:RandomLoadBalance(随机策略) 2.负载均衡策略存在以下五种: 2.1 RandomLoadBalance(随机) 2.2 Roun ...

  2. axis2 webservice入门学识(JS,Java,PHP调用实例源码)

    来源:http://www.myexception.cn/web/952419.html axis2 webservice入门知识(JS,Java,PHP调用实例源码) 背景简介 最近接触到一个银行接 ...

  3. 易语言操作java窗口,易语言调用JAVA源码

    易语言调用JAVA源码 @510835147.版本 2 .支持库 Javalib .支持库 spec .程序集 窗口程序集1 .程序集变量 jvm, Java虚拟机 .程序集变量 java, Java ...

  4. django-admin的源码流程

    一.admin的源码流程 首先可以确定的是:路由关系一定对应一个视图函数 a.当点击运行的时候,会先找到每一个app中的admin.py文件,并执行 settings---->INSTALLED ...

  5. MediaPlayer源码流程简要分析

    涉及文件目录: \frameworks\base\media\java\android\media\MediaPlayer.java \frameworks\base\media\jni\androi ...

  6. android 虚拟按键源码流程分析

    android 虚拟按键流程分析 今天来说说android 的虚拟按键的源码流程.大家都知道,android 系统的状态栏,虚拟按键,下拉菜单,以及通知显示,keyguard 锁屏都是在framewo ...

  7. SpringSecurity详细介绍RememberMe源码流程

      本文我们来详细看看rememberMe的源码流程 rememberMe源码分析   首先我们要搞清楚rememberMe功能应该是在认证成功后才能具有的,所以我们应该从Usernamepasswo ...

  8. 【七夕特殊礼物】Dubbo学习之SPI实战与debug源码

    目录 绪论 环境搭建 dubbo-demo-interface dubbo-demo-xml dubbo-demo-xml-provider 源码跟踪 getExtension createExten ...

  9. phpcms 指定id范围 调用_Dubbogo 源码笔记(二)客户端调用过程

    作者 | 李志信 导读:有了上一篇文章<Dubbo-go 源码笔记(一)Server 端开启服务过程>的铺垫,可以类比客户端启动于服务端的启动过程.其中最大的区别是服务端通过 zk 注册服 ...

最新文章

  1. python 中的下划线
  2. OpenVAS 开放式漏洞评估系统--安装及说明
  3. 【步态识别】基于深度学习的步态识别系统的MATLAB仿真,包括ALEXNET,改进CNN,GOOGLENET
  4. 杰出数据科学家的关键技能是什么?
  5. bmp转换tiff c++代码_Creative Convert for Mac(文件格式转换工具)
  6. NSubstitute完全手册(一)入门基础
  7. OpenSSL 创建自签名证书
  8. 动态规划:任务调度问题(双塔问题)
  9. 笔记本电脑性价比排行2019_笔记本电脑性价比排行2020榜单介绍
  10. MySql cmd下的学习笔记 —— 有关select的操作(order by,limit)
  11. 网易云部分 解析歌词
  12. 阿铭Linux_网站维护学习笔记20190416
  13. Tomcat SSL配置 Connector attribute SSLCertificateFile must be defined when using SSL with APR解决 作者:孤风一
  14. indexof 的基本用法
  15. 奇怪的google博客搜索
  16. mysql 删除一条数据sql语句_sql删除语句
  17. 【Ice】【01】linux 安装ice
  18. 微信 android兼容性问题怎么解决方案,微信7.0版本与EMUI系统兼容性问题,华为官方是这样回复的...
  19. calendar java 线程安全_Calendar(线程不安全)
  20. 使用libxl库读取excel文件

热门文章

  1. 瘦子的肠道菌群和胖子的区别_瘦子和病态肥胖患者肠道菌群组成和潜在功能的显著差异...
  2. [转载] Java中的50个关键字
  3. qgis在地图上画导航线_在Laravel中的航线
  4. 逻辑回归 数据_数据科学中的逻辑回归
  5. c# uri.host_C#| Uri.HostNameType属性与示例
  6. 图片人脸检测——Dlib版(四)
  7. React Native顶|底部导航使用小技巧
  8. NHibernate使用之详细图解
  9. C# struct 装箱拆箱例子
  10. 关于CentOS-6的默认带的mysql启动和安装问题