2.Dubbo源碼閱讀-配置篇

Dubbo的分支: 3.0
Dubbo的服務提供者會將RPC服務的調用說明,導出到配置中心。然后服務的消費者向配置中心訂閱這些服務,也就是引用這些服務。

服務端-服務提供方-暴露/導出服務

  1. dubbo根據spring的擴展api,增加了dubbo命名空間,以及各種xml元素,用來配置服務。

    1. 關于dubbo和spring的集成,以及spring對自定義的命名空間解析,詳見NamespaceHandler,NamespaceHandlerSupport的使用。
  2. spring boot啟動,解析dubbo框架xml配置文件,裝載實例,放入spring容器,到這相當于實例化bean完成。

  3. dubbo增加了應用上下文監聽類DubboBootstrapApplicationListener,類內覆蓋onApplicationContextEvent(ApplicationContextEvent event)方法,啟動dubbo。

    @Override
    public void onApplicationContextEvent(ApplicationContextEvent event) {
        //使用事件監聽器的方式與spring集成
        if (event instanceof ContextRefreshedEvent) {
            onContextRefreshedEvent((ContextRefreshedEvent) event);
        } else if (event instanceof ContextClosedEvent) {
            onContextClosedEvent((ContextClosedEvent) event);
        }
    }
    
    private void onContextRefreshedEvent(ContextRefreshedEvent event) {
        //啟動dubbo
        dubboBootstrap.start();
    }
    
    
  4. org.apache.dubbo.config.bootstrap.DubboBootstrap#start

    1. 如果是第一次啟動的話,調用initialize()完成spi加載、配置中心初始化、事件監聽等初始化。

      public DubboBootstrap start() {
          if (started.compareAndSet(false, true)) {
              startup.set(false);
              initialize();
              //start方法內有兩行關鍵的代碼,一個是服務提供方開始暴露/導出所有服務,一個是消費方開始發現/引用服務
           
              // 1. 導出dubbo所有服務
              exportServices();
      
              // Not only provider register
              if (!isOnlyRegisterProvider() || hasExportedServices()) {
                  // 2. export MetadataService
                  exportMetadataService();
                  //3. Register the local ServiceInstance if required
                  registerServiceInstance();
              }
      
              //消費者引用服務
              referServices();
              
              //省略...
          }
          return this;
      }
      
    2. 調用exportServices暴露/導出服務。

      private void exportServices() {
          //從配置中心拿到配置的所有service bean,依次導出。
          configManager.getServices().forEach(sc -> {
              ServiceConfig serviceConfig = (ServiceConfig) sc;
              serviceConfig.setBootstrap(this);
      
              if (exportAsync) {
                  //異步導出
                  ExecutorService executor = executorRepository.getServiceExporterExecutor();
                  Future<?> future = executor.submit(() -> {
                      sc.export();
                      exportedServices.add(sc);
                  });
                  asyncExportingFutures.add(future);
              } else {
                  //同步導出
                  sc.export();
                  exportedServices.add(sc);
              }
          });
      }
      
    3. 遍歷所有服務,依次調用org.apache.dubbo.config.ServiceConfig#export,導出服務。最終調用org.apache.dubbo.config.ServiceConfig#doExportUrls,對該服務配置的暴露協議,每個協議暴露一份服務。

      private void doExportUrls() {
          //repository.services存有所有的服務描述對象,包括默認加載的隱含服務,
          //如EchoService,GenericService,MonitorService,MetricsService
          //repository.providers存有所有的服務提供者對象
          //repository.consumers存有所有的服務消費者對象
          ServiceRepository repository = ApplicationModel.getServiceRepository();
          
          //此處省略部分代碼......
          
          //獲取向配置中心注冊的服務url格式,即registry://xxxx
          List<URL> registryURLs = ConfigValidationUtils.loadRegistries(this, true);
      
          for (ProtocolConfig protocolConfig : protocols) {
              //此處省略部分代碼......
              //因為一個服務可以使用多種通訊協議,所以此處是每個協議導出一份服務配置,如dubbo/http/injvm協議各一份
              doExportUrlsFor1Protocol(protocolConfig, registryURLs);
          }
      }
      
    4. 導出的核心方法:org.apache.dubbo.config.ServiceConfig#doExportUrlsFor1Protocol

      1. 因為dubbo設計的是使用url作為配置總線,代表最終的導出格式。這個方法會先補充url信息,如失敗重試retry、token、Generic服務等數據。
      2. 判斷一個服務只要不是配置了只能暴露給遠程的話,就會導出一份本地injvm服務,即調用exportLocal(url)。
      3. 暴露給遠程。

單獨說下導出一個服務的主要邏輯

  1. 這里需要先解釋下dubbo設計的幾個類:Protocol/Exporter/Invoker,說下這三個類的關系。

    1. Protocol是協議的意思,比如Dubbo、Injvm協議。一個服務要以某個協議的方式暴露出來,即可以用這個協議來訪問這個服務,如HelloService以http協議的方式暴露出來,也就是以后可以用http的方式訪問這個服務。
    2. Dubbo抽象出來一個Exporter的概念,負責如何暴露服務,如何取消服務暴露。 一個服務被暴露出來,需要知道它該怎么調用,就有了Invoker。Invoker負責實際的調用服務返回響應。
    3. 綜上,1個Protocol包含多個服務的Exporter,1個Exporter在大部分情況下只包含1個Invoker。
    4. 從代碼來看三者的關系:
    public abstract class AbstractProtocol implements Protocol {
    
        //該協議的所有導出者,即暴露的所有服務
        //"greeting/org.apache.dubbo.demo.GreetingService:1.0.0:20880" -> DubboExporter對象
        //"greeting/org.apache.dubbo.demo.GreetingService:1.0.0" -> InjvmExporter對象
        protected final Map<String, Exporter<?>> exporterMap = new ConcurrentHashMap<String, Exporter<?>>();
    
        //省略......
    }
    
    public abstract class AbstractExporter<T> implements Exporter<T> {
    
        //每個AbstractExporter子類都會至少包含一個Invoker
        private final Invoker<T> invoker;
    
        //省略......
    }
    
  2. 再說下這三者的組裝過程:

    public class ServiceConfig<T> extends ServiceConfigBase<T> {
        //自適應Protocol,默認以DubboProtocol為基礎,按優先級在外層包裝各種包裝類,如:ProtocolFilterWrapper,ProtocolListenerWrapper,QosProtocolWrapper
        private static final Protocol PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
    
        //Invoker動態代理工廠,默認為JavassistProxyFactory
        private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
    
        //所有導出的服務
        private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();
    
        private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
            //省略...
            if (CollectionUtils.isNotEmpty(registryURLs)) {
                for (URL registryURL : registryURLs) {
                    //省略...
                    //使用JavassistProxyFactory動態生成、加載invoker字節碼。以GreetingService舉例
                    //ref=greetingServiceImpl,interfaceClass=GreetingService.class,第三個參數是url=injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService ?anyhost=true&application=demo-provider&bind.ip=192.168.2.3&bind.port=20880 &deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata &mapping.type=metadata&metadata-type=remote&methods=hello&pid=25062&qos.port=22222 &release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1615949370199&version=1.0.0
                    //JavassistProxyFactory.getInvoker方法內部會先生成invoker wrapper包裝類,目的是統一bean的方法調用。生成的wrapper類詳見下面Wrapper1。
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                    //使用JavassistProxyFactory動態生成、加載invoker字節碼。以GreetingService舉例
                    //ref=greetingServiceImpl,interfaceClass=GreetingService.class,
                    //第三個參數是url=injvm://127.0.0.1/org.apache.dubbo.demo.GreetingService?anyhost=true&application=demo-provider
                    // &bind.ip=192.168.2.3&bind.port=20880 &deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&group=greeting
                    // &interface=org.apache.dubbo.demo.GreetingService&mapping-type=metadata &mapping.type=metadata
                    // &metadata-type=remote&methods=hello&pid=25062&qos.port=22222 &release=&revision=1.0.0&side=provider
                    // &timeout=5000&timestamp=1615949370199&version=1.0.0
                    //JavassistProxyFactory.getInvoker方法內部會先生成invoker wrapper包裝類。生成的wrapper類詳見下面Wrapper1。
                    Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(EXPORT_KEY, url.toFullString()));
                    DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                    //將invoker以某個協議暴露出去,返回exporter
                    //在protocol的多層包裝對象中,有一個ProtocolFilterWrapper,調用export方法時,它會先判斷是否為registry協議,如果不是,
                    //需要先構造invoker的過濾器鏈,增強invoker功能,如MonitorFilter,TimeoutFilter,TraceFilter,ExceptionFilter等,再繼續導出。
                    Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                    exporters.add(exporter);
                }
            } else {
                //省略,基本同上
                Invoker<?> invoker = PROXY_FACTORY.getInvoker(ref, (Class) interfaceClass, url);
                DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
    
                Exporter<?> exporter = PROTOCOL.export(wrapperInvoker);
                exporters.add(exporter);
            }
        }
    }
    
  3. 服務如何暴露給遠程,即向公共配置中心注冊服務

    1. 上面說過我們在xml中配置一個服務,可以指定它以哪些協議的方式暴露出來,比如dubbo,injvm,http,grpc,hessian等

    2. dubbo會遍歷所有協議,每一個協議都會調用doExportUrlsFor1Protocol()來導出對應格式的服務。

    3. dubbo會判斷如果這個協議配置了暴露給遠程,那么會根據當前協議的url,創建一個用于注冊registry的url。如,當前是http協議,框架判斷需要暴露給遠程,即需要注冊到公共配置中心,那么會根據http://xxx,生成一個registry://xxx的url,用于注冊服務。

    4. 服務導出是由協議對象調用export()觸發,Exporter<?> exporter = PROTOCOL.export(wrapperInvoker)。其中registry://協議是由RegistryProtocol.export負責導出。

      @Override
      public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
          //orginInvoker的url=registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?xxx
          //得到registryUrl=zookeeper://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?xxx
          URL registryUrl = getRegistryUrl(originInvoker);
      
          //得到providerUrl=dubbo://192.168.2.3:20880/org.apache.dubbo.demo.GreetingService?xxx
          URL providerUrl = getProviderUrl(originInvoker);
      
          //省略...
      
          // decide if we need to delay publish
          boolean register = providerUrl.getParameter(REGISTER_KEY, true);
          if (register) {
              //立即向配置中心如zk,注冊服務
              register(registryUrl, registeredProviderUrl);
          }
      
          //省略...
      
          //Ensure that a new exporter instance is returned every time export
          return new DestroyableExporter<>(exporter);
      }
      
      private void register(URL registryUrl, URL registeredProviderUrl) {
          //根據url協議頭,獲取配置中心注冊器對象,得到ZookeeperRegistry
          Registry registry = registryFactory.getRegistry(registryUrl);
          //調用ZookeeperRegistry的注冊方法,內部為使用CuratorZookeeperClient向配置中心寫服務數據
          //到這里也就完成了將服務注冊到公共配置中心,暴露給遠程了
          registry.register(registeredProviderUrl);
      }
      
    5. 到此服務暴露完畢。

客戶端-服務消費方-發現/引用服務

先需要理解消費方的幾個重要概念

  1. 先舉個例子,借著例子來理解。假設在北京和上海分別有一個注冊中心zk-beijing,zk-shanghai。在北京有三臺機器,ip分別為1.1.1.1/2.2.2.2/3.3.3.3。其中1.1.1.1為消費者,2.2.2.2和3.3.3.3為服務提供者。2.2.2.2提供ServiceA,ServiceB兩個服務,3.3.3.3提供ServiceA,ServiceC兩個服務。
    1. 我們先簡化環境,看下只有一個注冊中心的情況,即只向zk-beijing注冊。在2.2.2.2/3.3.3.3啟動初始化時,會向注冊中心注冊暴露服務,路徑為:

      //zookeeper路徑結構
      /dubbo
          /ServiceA
              /providers
                  /URL.encode(dubbo://2.2.2.2:20880/ServiceA?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceA&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=37004&release=&side=provider&timeout=3000&timestamp=1617773391401)
                  /URL.encode(dubbo://3.3.3.3:20880/ServiceA?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceA&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=sayHello,sayHelloAsync&pid=35111&release=&side=provider&timeout=3000&timestamp=1617773391507)
          /ServiceB
              /providers
                  /URL.encode(rest://2.2.2.2:80/ServiceB?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceB&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=buy,sell&pid=37004&release=&revision=1.0.0&side=provider&timeout=5000&timestamp=1617773396526&version=1.0.0)
          /ServiceC
              /providers
                  /URL.encode(dubbo://3.3.3.3:20880/ServiceC?anyhost=true&application=demo-provider&delay=5000&deprecated=false&dubbo=2.0.2&dynamic=true&generic=false&interface=ServiceC&mapping-type=metadata&mapping.type=metadata&metadata-type=remote&methods=go,back&pid=351111&release=&side=provider&timeout=3000&timestamp=1617773391507)
      
    2. 從路徑可以看出,dubbo不是以一個機器為基本單位,暴露該機器下有哪些服務的,如:/dubbo/2.2.2.2/我的服務xxx。dubbo是以服務為單位來暴露。因為我們引用都是引用哪個服務,暴露也應該是暴露哪個服務,服務下標明哪些機器上有該服務。你需要引用哪些服務就可以隨意的引用哪些,不需要引用整個機器暴露出來的所有服務。假如一個服務暴露在多臺機器上,就更是個問題了。

    3. 再看多注冊中心,假如有另外幾臺機器,向shanghai注冊,情況和beijing差不多。這種多注冊中心存在時,可以看做有兩個異地服務集群,比如HelloService有北京和上海兩個服務集群。

概念一:Invoker

  1. Invoker是什么:從zk樹上看,/providers下的每一個服務url在消費方都會對應一個Invoker。如果1.1.1.1引用了這三個服務,那就會創建4個對應的Invoker。一個Invoker表示一個消費方持有的,對一個提供方某一個服務的引用。Invoker內包含連接池屬性字段ExchangeClient[] clients。因為ClusterInvoker的存在,為了區分,Invoker也叫做普通Invoker。

概念二:Directory

  1. Directory就是多個Invoker的集合,實現類一般會有一個List<Invoker<T>>屬性字段,存儲Invoker。為了方便表示以及操作多Invoker,抽象出來的概念。

概念三:Router

  1. Router路由。在實際使用中,可能由于我們部署的物理機性能有差別,有幾臺機器性能特別好,或者說為了測試某幾臺機器上的服務,要求請求更多的或者全部路由到某幾臺機器,Router負責的就是這個路由邏輯。Router會從Directory中遍歷選出與路由規則匹配的invoker子集,然后交給負載均衡器。

概念四:LoadBalance

  1. LoadBalance(負載均衡器)接收Router篩選出來的可以使用的invoker集合,從中選出一個普通invoker,用于發起實際調用。

概念五:ClusterInvoker

  1. ClusterInvoker就是實際的某一個物理集群了,如HelloService服務的北京集群。

概念六:Cluster

  1. Cluster是對實際物理集群抽象出的最上層概念,即某一個服務的所有物理集群的集合叫做Cluster。如HelloService的北京和上海兩個異地物理集群合起來叫做Cluster。

總結

  1. HelloService服務部署在兩個異地集群,一個北京,一個上海。兩地各自都有一個注冊中心zk。
  2. 自下而上的組裝過程:
    1. dubbo會從北京的注冊中心zk讀取HelloService的所有提供者,一個提供者封裝成一個普通Invoker。
    2. 將多個普通Invoker存儲到一個Directory中。
    3. 根據業務特性,創建一個對應的集群執行器對象ClusterInvoker。選擇適合的Router和LoadBalance,將Directory傳入。到此北京集群的invoker封裝好了。
    4. 重復上述步驟,將上海集群也封裝成一個ClusterInvoker。
    5. 將北京、上海ClusterInvoker封裝到多注冊中心Cluster對象中。
    6. 服務以Cluster為入口,對外提供服務。
  3. 自上而下的調用過程:
    1. Cluster會根據訂閱的各個注冊中心的配置、與服務消費方是否處于同一個物理集群、權重等先選出來一個調用的ClusterInvoker,即先選出來一個物理集群。假如選出來的是北京集群。

    2. 然后在北京集群ClusterInvoker執行Router路由,選出匹配的普通Invoker集合。

    3. 在路由后的Invoker集合上,執行負載均衡,由LoadBalance選出一個最終用來執行RPC的普通Invoker。

    4. 執行Invoker.invoke(Invocation invocation),發起調用。

      //從代碼看下調用過程
      public abstract class AbstractClusterInvoker<T> implements ClusterInvoker<T> {
          public Result invoke(final Invocation invocation) throws RpcException {
              //省略
              //使用路由器Router從ClusterInvoker.directory存儲的所有的invoker中,路由出符合路由規則的一批invoker。
              List<Invoker<T>> invokers = list(invocation);
              //上面選出了方向正確的一批invoker,現在需要負載均衡選一個執行了。此處初始化負載均衡器LoadBalance。
              LoadBalance loadbalance = initLoadBalance(invokers, invocation);
              //為異步執行附著調用id
              RpcUtils.attachInvocationIdIfAsync(getUrl(), invocation);
              //使用負載均衡器選一個invoker,發起調用,返回響應。
              return doInvoke(invocation, invokers, loadbalance);
          }
      }
      

消費方發現/引用服務的過程

  1. Dubbo是嚴重依賴Spring框架的,我們在xml配置了java interface > HelloService的Bean聲明和引用關系后,如何在服務消費方生成、加載該接口的實現類,創建實例對象,納入到Spring容器管理,在需要的地方注入。
  2. Dubbo是由FactoryBean入手的,過程與MyBatis的Mapper類生成過程類似。(注:3.0分支中,只有延遲加載的代理對象是使用FactoryBean功能生成的,普通代理對象是使用ReferenceConfig.get()生成的。而在master分支中,所有代理對象都是使用FactoryBean生成,底層實現也是調用的ReferenceConfig.get())
  3. 下面說的是3.0分支普通代理對象的生成過程,沒有使用FactoryBean。
  1. 與服務暴露一樣,服務引用也是使用事件監聽的方式與spring集成,都會調用的org.apache.dubbo.config.bootstrap.DubboBootstrap#start方法,該方法上面說過,有兩個關鍵的點,一個是exportServices():提供方開始暴露/導出所有服務,另一個是referServices():消費方開始發現/引用服務。

    public DubboBootstrap start() {
        if (started.compareAndSet(false, true)) {
            startup.set(false);
            initialize();
            //start方法內有兩行關鍵的代碼,一個是服務提供方開始暴露/導出所有服務,一個是消費方開始發現/引用服務
         
            // 1. 導出dubbo所有服務
            exportServices();
    
            // Not only provider register
            if (!isOnlyRegisterProvider() || hasExportedServices()) {
                // 2. export MetadataService
                exportMetadataService();
                //3. Register the local ServiceInstance if required
                registerServiceInstance();
            }
    
            //消費者引用服務
            referServices();
            
            //省略...
        }
        return this;
    }
    
  2. referServices()中會觸發緩存數據的初始化、加載。

    private void referServices() {
        //configManager.getReferences()拿到的是對xml中配置的所有<dubbo:reference xxx/>
        //解析后封裝的ReferenceConfig對象
        configManager.getReferences().forEach(rc -> {
            //省略...
            //cache為ReferenceConfigCache類型,底層是一個ConcurrentHashMap,
            //調用get會初始化對應的ReferenceConfig對象,放入緩存中
            cache.get(rc);
        });
    }
    
  3. ReferenceConfigCache.get(rc),從緩存拿消費方引用的服務代理對象,如果沒有觸發實現類的生成、加載,然后創建代理類放入cache。

    public <T> T get(ReferenceConfigBase<T> referenceConfig) {
        //key=org.apache.dubbo.demo.DemoService
        String key = generator.generateKey(referenceConfig);
        //type=DemoService.class
        Class<?> type = referenceConfig.getInterfaceClass();
        //proxies結構=< DemoService.class, <"org.apache.dubbo.demo.DemoService", DemoService實現類代理對象> >
        proxies.computeIfAbsent(type, _t -> new ConcurrentHashMap<>());
        //proxiesOfType結構=<"org.apache.dubbo.demo.DemoService", DemoService實現類代理對象>
        ConcurrentMap<String, Object> proxiesOfType = proxies.get(type);
        proxiesOfType.computeIfAbsent(key, _k -> {
            //由ReferenceConfig配置對象觸發對應服務接口實現類的創建、加載,以及代理對象的創建。
            //proxy就是在消費方引用的服務代理對象
            Object proxy = referenceConfig.get();
            referredReferences.put(key, referenceConfig);
            return proxy;
        });
    
        return (T) proxiesOfType.get(key);
    }
    
  4. 關鍵的來了,ReferenceConfig類包含了生成、加載實現類,創建代理對象的主要邏輯。

    public class ReferenceConfig<T> extends ReferenceConfigBase<T> {
        //缺省FailoverCluster
        private static final Cluster CLUSTER = ExtensionLoader.getExtensionLoader(Cluster.class).getAdaptiveExtension();
        //缺省JavassistProxyFactory
        private static final ProxyFactory PROXY_FACTORY = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
        //根據url協議類型決定,url=registry://xxx,對應RegistryProtocol。url=dubbo://xxx,對應DubboProtocol。url=injvm://xxx,對應InjvmProtocol。
        private static final Protocol REF_PROTOCOL = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
        //ClusterInvoker類型
        private transient volatile Invoker<?> invoker;
        //直連提供者地址或者注冊中心地址
        protected final List<URL> urls = new ArrayList<URL>();
        //引用的代理對象
        private transient volatile T ref;
        //1.生成的代理對象會set到屬性字段中,為null的話,開始引用初始化。
        public synchronized T get() {
            if (ref == null) {
                //觸發引用初始化
                init();
            }
            return ref;
        }
        //2. 初始化
        public synchronized void init() {
            if (initialized) return;
    
            //解析各種消費方配置,放入map
            //省略...
    
            //map={"mapping-type":"metadata","init":"false","side":"consumer","register.ip":"192.168.2.3",
            // "release":"","methods":"sayHello,sayHelloAsync","qos.port":"33333","provided-by":"demo-provider",
            // "dubbo":"2.0.2","pid":"35116","check":"true","interface":"org.apache.dubbo.demo.DemoService",
            // "enable.auto.migration":"true","mapping.type":"metadata","metadata-type":"remote",
            // "application":"demo-consumer","sticky":"false","timestamp":"1617676272015","enable-auto-migration":"true"}
            //為org.apache.dubbo.demo.DemoService接口創建消費方代理對象
            ref = createProxy(map);
    
            //分發ReferenceConfigInitializedEvent事件
            dispatch(new ReferenceConfigInitializedEvent(this, invoker));
        }
    
        //3.創建動態代理對象
        private T createProxy(Map<String, String> map) {
            //從配置中找出是否應該引用同一JVM中的服務。默認行為為true
            if (shouldJvmRefer(map)) {
                //injvm本地引用
                URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
                //InjvmProtocol.refer返回InjvmInvoker類型
                invoker = REF_PROTOCOL.refer(interfaceClass, url);
            } else {
                //遠程引用
                //url為配置的peer-to-peer調用地址,或者注冊中心地址
                //假如服務集群有5臺機器,你只想測試調用其中的A,B兩臺機器,配置的就是直連提供者AB的地址
                //<dubbo:reference id="as" interface="AService" url="dubbo://1.1.1.1:20890;第二提供者地址"/>
    
                if (urls.size() == 1) {
                    //一個服務只有一個url,有兩種情況:1.只有一個注冊中心;2.直連一個提供者。
                    //這里返回的invoker一定是ClusterInvoker子類型,默認為FailoverClusterInvoker,
                    //注冊中心的提供者或者直連url都會轉為普通invoker->directory,再set到ClusterInvoker中返回
                    //如果url.protocol=service-discovery-registry,對應的protocol為:RegistryProtocol
                    invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
                } else {
                    //一個服務多個url,可能有三種情況:1.多個注冊中心;2.直連多個提供者。3.兩者共存,即url配了注冊中心地址和直連提供者地址,這種情況不確定可不可以,沒試出來。
                    //如果url中有注冊協議,即從注冊中心訂閱服務,返回的invoker為ZoneAwareClusterInvoker類型
                    //無注冊協議時,返回FailoverClusterInvoker
                    if (registryURL != null) {
                        String cluster = registryURL.getParameter(CLUSTER_KEY, ZoneAwareCluster.NAME);
                        invoker = Cluster.getCluster(cluster, false).join(new StaticDirectory(registryURL, invokers));
                    } else {
                        String cluster = "省略...";
                        invoker = Cluster.getCluster(cluster).join(new StaticDirectory(invokers));
                    }
                }
            }
    
            //JavassistProxyFactory.getProxy
            return (T) PROXY_FACTORY.getProxy(invoker, ProtocolUtils.isGeneric(generic));
        }   
    }
    
  5. 到這就完成了消費者引用服務代理對象,其中Cluster和Invoker的組裝過程,就是上面提到的。

  6. 補充下master分支中,改版的使用FactoryBean生成代理對象邏輯:

    public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean,
            ApplicationContextAware, InitializingBean, DisposableBean {
    
        @Override
        public Object getObject() {
            //調用ReferenceConfig.get()生成代理對象
            return get();
        }
    
        @Override
        public Class<?> getObjectType() {
            //返回服務接口class
            return getInterfaceClass();
        }
    
    }
    
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容