dubbo服务引用

dubbo是一款开源的高性能Java RPC框架,可以像调用本地函数一样,调用远程服务。下面对dubbo服务引用部分的源码进行分析, 以dubbo-demo-xml为例进行说明, 版本为2.7.6-SNAPSHOT,不同版本之间代码会有细微差别,但是核心思想是一致的。学习dubbo源码最好的方式是将demo代码运行起来,然后调试去看。
主要代码有:
Application.java

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/dubbo-consumer.xml");
context.start();
DemoService demoService = context.getBean(DemoService.class);
CompletableFuture<String> hello = demoService.sayHelloAsync("world");
System.out.println("result: " + hello.get());

dubbo-consumer.xml

    <dubbo:application name="demo-consumer"/>

    <dubbo:registry address="zookeeper://127.0.0.1:2181"/>

    <dubbo:reference id="demoService" check="false" interface="org.apache.dubbo.demo.DemoService"/>

dubbo服务的引用主要包括:

  1. 服务(DemoService)的BeanDefinition的初始化
    加载并解析配置(dubbo-consumer.xml),然后生成DemoService的BeanDefinition,注册到到IoC容器的beanDefinitionMap中
  2. 服务的实例化(DemoService的实例化)
    当根据DemoService查找其实现时,会根据demoService去beanDefinitionMap中查找定义,这里是ReferenceBean,又因为ReferenceBean实现了FactoryBean接口,所以会调用FactoryBean#getObject方法实例化

1 BeanDefinition的初始化

AbstractRefreshableApplicationContext#refreshBeanFactory
-> AbstractXmlApplicationContext#loadBeanDefinitions(DefaultListableBeanFactory)
->AbstractXmlApplicationContext#loadBeanDefinitions(XmlBeanDefinitionReader)
->AbstractBeanDefinitionReader#loadBeanDefinitions(String...)
->AbstractBeanDefinitionReader#loadBeanDefinitions(String, jSet<Resource>)
-> XmlBeanDefinitionReader#loadBeanDefinitions(EncodedResource)
->XmlBeanDefinitionReader#doLoadBeanDefinitions
-> XmlBeanDefinitionReader#registerBeanDefinitions
->DefaultBeanDefinitionDocumentReader#doRegisterBeanDefinitions
-> DefaultBeanDefinitionDocumentReader#parseBeanDefinitions
-> BeanDefinitionParserDelegate#parseCustomElement(Element, BeanDefinition)

经过长长的调用链,最终调用的是

    public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

其中this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri) 会调用DubboNamespaceHandler对dubbo的xml配置进行解析,DubboNamespaceHandler#init会执行如下代码,将xml中的dubbo:reference和ReferenceBean关联起来

registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false)); 

最终IoC容器中,beanDefinitionMap中demoService对应的是ReferenceBean是, 这也就解释了为什么根据类型(DemoService)查找其实现时,调用的是ReferenceBean的getObject方法

2. 服务引用

Dubbo服务引用的时机有两个,

  1. 在 Spring 容器调用 ReferenceBean 的 afterPropertiesSet 方法时引用服务,需要配置 <dubbo:reference> 的 init 属性开启
  2. 第二个是在 ReferenceBean 对应的服务被注入到其他类中时引用,默认情况。

其实主要看ReferenceBean#getObject的调用时机, getObject是服务引用的入口:
ReferenceBean#getObject
-> ReferenceConfig#get
-> ReferenceConfig#init
-> ReferenceConfig#createProxy
createProxy中的主要代码如下, 包括创建invoker和创建代理两步。

  // 创建invoker 
  invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))
  // 创建服务代理
 return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic))

2.1 创建invoker

Invoker 是 Dubbo 的核心模型,代表一个可执行体。在服务提供方,Invoker 用于调用服务提供类。在服务消费方,Invoker 用于执行远程调用。

invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))

REF_PROTOCOL是 Protocol$Adaptive, url.get(0).getProtocol为"registry" 所以首先调用的ProtocolListenerWrapper(ProtocolFilterWrapper(RegistryProtocol))#refer, RegistryProtocol的#refer 内部调用ProtocolListenerWrapper(ProtocolFilterWrapper(DubboProtocol))#refer

  1. ProtocolListenerWrapper(ProtocolFilterWrapper(RegistryProtocol))#refer 只是单纯地调用RegistryProtocol#refer
  public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
      url = getRegistryUrl(url);
      Registry registry = registryFactory.getRegistry(url);
      return doRefer(cluster, registry, type, url);
  }

registryFactory.getRegistry(url); 根据key从全局的注册中心集合中获取注册中心, 如果没有则创建
key为:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService
最终registry为: ZookeeperRegistry:
zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=demo-consumer&dubbo=2.0.2&interface=org.apache.dubbo.registry.RegistryService&pid=60701&qos.port=33333&timestamp=1597565872457
doRefer 包括注册服务消费者,即在zookeeper的consumers目录下新建节点, 从注册中心获取可用的服务提供者,通过netty建立连接。 最终得到的invoker对象如下:

2.2 创建代理

DemoService demoService = context.getBean(DemoService.class) 获得的是由proxyFactory.getProxy(invoker) 生成的代理对象
这样demoService对sayHelloAsync的调用,就可以变成对invoker的调用

通过ProxyGenerator,得到代理对象demoService的字节码,最终生成的代理对象为(去掉不相关的hashCode、equals、toString):

public final class proxy0 extends Proxy implements DC, Destroyable, EchoService, DemoService {
  private static Method m4;
  private static Method m6;
  private static Method m5;
  private static Method m3;

  public proxy0(InvocationHandler var1) throws  {
      super(var1);
  }

  public final Object $echo(Object var1) throws  {
      try {
          return (Object)super.h.invoke(this, m4, new Object[]{var1});
      } catch (RuntimeException | Error var3) {
          throw var3;
      } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
      }
  }

  public final String sayHello(String var1) throws  {
      try {
          return (String)super.h.invoke(this, m6, new Object[]{var1});
      } catch (RuntimeException | Error var3) {
          throw var3;
      } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
      }
  }


  public final CompletableFuture sayHelloAsync(String var1) throws  {
      try {
          return (CompletableFuture)super.h.invoke(this, m5, new Object[]{var1});
      } catch (RuntimeException | Error var3) {
          throw var3;
      } catch (Throwable var4) {
          throw new UndeclaredThrowableException(var4);
      }
  }

  public final void $destroy() throws  {
      try {
          super.h.invoke(this, m3, (Object[])null);
      } catch (RuntimeException | Error var2) {
          throw var2;
      } catch (Throwable var3) {
          throw new UndeclaredThrowableException(var3);
      }
  }


  static {
      try {
          m4 = Class.forName("com.alibaba.dubbo.rpc.service.EchoService").getMethod("$echo", Class.forName("java.lang.Object"));
          m6 = Class.forName("org.apache.dubbo.demo.DemoService").getMethod("sayHello", Class.forName("java.lang.String"));
          m5 = Class.forName("org.apache.dubbo.demo.DemoService").getMethod("sayHelloAsync", Class.forName("java.lang.String"));
          m3 = Class.forName("org.apache.dubbo.rpc.service.Destroyable").getMethod("$destroy");
      } catch (NoSuchMethodException var2) {
          throw new NoSuchMethodError(var2.getMessage());
      } catch (ClassNotFoundException var3) {
          throw new NoClassDefFoundError(var3.getMessage());
      }
  }
}

参考:

http://dubbo.apache.org/zh-cn/docs/source_code_guide/refer-service.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,186评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,858评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,620评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,888评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,009评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,149评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,204评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,956评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,385评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,698评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,863评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,544评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,185评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,899评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,141评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,684评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,750评论 2 351