dubbo服務(wù)引用

dubbo是一款開源的高性能Java RPC框架,可以像調(diào)用本地函數(shù)一樣,調(diào)用遠(yuǎn)程服務(wù)。下面對(duì)dubbo服務(wù)引用部分的源碼進(jìn)行分析, 以dubbo-demo-xml為例進(jìn)行說(shuō)明, 版本為2.7.6-SNAPSHOT,不同版本之間代碼會(huì)有細(xì)微差別,但是核心思想是一致的。學(xué)習(xí)dubbo源碼最好的方式是將demo代碼運(yùn)行起來(lái),然后調(diào)試去看。
主要代碼有:
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服務(wù)的引用主要包括:

  1. 服務(wù)(DemoService)的BeanDefinition的初始化
    加載并解析配置(dubbo-consumer.xml),然后生成DemoService的BeanDefinition,注冊(cè)到到IoC容器的beanDefinitionMap中
  2. 服務(wù)的實(shí)例化(DemoService的實(shí)例化)
    當(dāng)根據(jù)DemoService查找其實(shí)現(xiàn)時(shí),會(huì)根據(jù)demoService去beanDefinitionMap中查找定義,這里是ReferenceBean,又因?yàn)镽eferenceBean實(shí)現(xiàn)了FactoryBean接口,所以會(huì)調(diào)用FactoryBean#getObject方法實(shí)例化

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)

經(jīng)過長(zhǎng)長(zhǎng)的調(diào)用鏈,最終調(diào)用的是

    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) 會(huì)調(diào)用DubboNamespaceHandler對(duì)dubbo的xml配置進(jìn)行解析,DubboNamespaceHandler#init會(huì)執(zhí)行如下代碼,將xml中的dubbo:reference和ReferenceBean關(guān)聯(lián)起來(lái)

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

最終IoC容器中,beanDefinitionMap中demoService對(duì)應(yīng)的是ReferenceBean是, 這也就解釋了為什么根據(jù)類型(DemoService)查找其實(shí)現(xiàn)時(shí),調(diào)用的是ReferenceBean的getObject方法

2. 服務(wù)引用

Dubbo服務(wù)引用的時(shí)機(jī)有兩個(gè),

  1. 在 Spring 容器調(diào)用 ReferenceBean 的 afterPropertiesSet 方法時(shí)引用服務(wù),需要配置 <dubbo:reference> 的 init 屬性開啟
  2. 第二個(gè)是在 ReferenceBean 對(duì)應(yīng)的服務(wù)被注入到其他類中時(shí)引用,默認(rèn)情況。

其實(shí)主要看ReferenceBean#getObject的調(diào)用時(shí)機(jī), getObject是服務(wù)引用的入口:
ReferenceBean#getObject
-> ReferenceConfig#get
-> ReferenceConfig#init
-> ReferenceConfig#createProxy
createProxy中的主要代碼如下, 包括創(chuàng)建invoker和創(chuàng)建代理兩步。

  // 創(chuàng)建invoker 
  invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0))
  // 創(chuàng)建服務(wù)代理
 return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic))

2.1 創(chuàng)建invoker

Invoker 是 Dubbo 的核心模型,代表一個(gè)可執(zhí)行體。在服務(wù)提供方,Invoker 用于調(diào)用服務(wù)提供類。在服務(wù)消費(fèi)方,Invoker 用于執(zhí)行遠(yuǎn)程調(diào)用。

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

REF_PROTOCOL是 Protocol$Adaptive, url.get(0).getProtocol為"registry" 所以首先調(diào)用的ProtocolListenerWrapper(ProtocolFilterWrapper(RegistryProtocol))#refer, RegistryProtocol的#refer 內(nèi)部調(diào)用ProtocolListenerWrapper(ProtocolFilterWrapper(DubboProtocol))#refer

  1. ProtocolListenerWrapper(ProtocolFilterWrapper(RegistryProtocol))#refer 只是單純地調(diào)用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); 根據(jù)key從全局的注冊(cè)中心集合中獲取注冊(cè)中心, 如果沒有則創(chuàng)建
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 包括注冊(cè)服務(wù)消費(fèi)者,即在zookeeper的consumers目錄下新建節(jié)點(diǎn), 從注冊(cè)中心獲取可用的服務(wù)提供者,通過netty建立連接。 最終得到的invoker對(duì)象如下:

2.2 創(chuàng)建代理

DemoService demoService = context.getBean(DemoService.class) 獲得的是由proxyFactory.getProxy(invoker) 生成的代理對(duì)象
這樣demoService對(duì)sayHelloAsync的調(diào)用,就可以變成對(duì)invoker的調(diào)用

通過ProxyGenerator,得到代理對(duì)象demoService的字節(jié)碼,最終生成的代理對(duì)象為(去掉不相關(guān)的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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容