源碼分析Dubbo服務提供者啓動流程-上篇

   本節將詳細分析Dubbo服務提供者的啓動流程,請帶着以下幾個疑問進行本節的閱讀,由於這幾個問題將是接下來幾篇文章分析的重點內容。web

  1. 何時創建與註冊中心的鏈接。
  2. 服務提供者何時向註冊中心註冊服務。
  3. 服務提供者與註冊中心的心跳機制。

   從上文中咱們得知,服務提供者啓動的核心入口爲ServiceBean,本節將從源碼級別詳細剖析ServcieBean的實現原理,即Dubbo服務提供者的啓動流程,ServiceBean的繼承層次如圖所示,dubbo:service標籤的全部屬性都被封裝在此類圖結構中。
這裏寫圖片描述spring

一、源碼分析ServiceBean#afterPropertiesSet
   ServiceBean#afterPropertiesSetapi

if (getProvider() == null) {  // @1
    Map<String, ProviderConfig> provide
             ConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class,   false, false); // @2
              // ......  具體解析代碼省略。
    }
}

   Step1:若是provider爲空,說明dubbo:service標籤未設置provider屬性,若是一個dubbo:provider標籤,則取該實例,若是存在多個dubbo:provider配置則provider屬性不能爲空,不然拋出異常:「Duplicate provider configs」。網絡

   ServiceBean#afterPropertiesSetapp

if (getApplication() == null
         && (getProvider() == null || getProvider().getApplication() == null)) {
       Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
                  ApplicationConfig.class, false, false);
        // ...省略
 }

   Step2:若是application爲空,則嘗試從BeanFactory中查詢dubbo:application實例,若是存在多個dubbo:application配置,則拋出異常:「Duplicate application configs」。
   Step3:若是ServiceBean的module爲空,則嘗試從BeanFactory中查詢dubbo:module實例,若是存在多個dubbo:module,則拋出異常:"Duplicate module configs: "。
   Step4:嘗試從BeanFactory中加載全部的註冊中心,注意ServiceBean的List< RegistryConfig> registries屬性,爲註冊中心集合。
   Step5:嘗試從BeanFacotry中加載一個監控中心,填充ServiceBean的MonitorConfig monitor屬性,若是存在多個dubbo:monitor配置,則拋出"Duplicate monitor configs: "。
   Step6:嘗試從BeanFactory中加載全部的協議,注意:ServiceBean的List< ProtocolConfig> protocols是一個集合,也即一個服務能夠經過多種協議暴露給消費者。dom

   ServiceBean#afterPropertiesSetjvm

if (getPath() == null || getPath().length() == 0) {
       if (beanName != null && beanName.length() > 0 && getInterface() != null && getInterface().length() > 0  && beanName.startsWith(getInterface())) {
                setPath(beanName);
       }
 }

   Step7:設置ServiceBean的path屬性,path屬性存放的是dubbo:service的beanName(dubbo:service id)。socket

   ServiceBean#afterPropertiesSetide

if (!isDelay()) {
     export();
}

   Step8:若是爲啓用延遲暴露機制,則調用export暴露服務。首先看一下isDelay的實現,而後重點分析export的實現原理(服務暴露的整個實現原理)。svg

   ServiceBean#isDelay

private boolean isDelay() {
        Integer delay = getDelay();
        ProviderConfig provider = getProvider();
        if (delay == null && provider != null) {
            delay = provider.getDelay();
        }
        return supportedApplicationListener && (delay == null || delay == -1);
    }

   若是有設置dubbo:service或dubbo:provider的屬性delay,或配置delay爲-1,都表示啓用延遲機制,單位爲毫秒,設置爲-1,表示等到Spring容器初始化後再暴露服務。從這裏也能夠看出,Dubbo暴露服務的處理入口爲ServiceBean#export—》ServiceConfig#export。

1.1 源碼分析ServiceConfig#export 暴露服務
   調用鏈:ServiceBean#afterPropertiesSet------>ServiceConfig#export

public synchronized void export() {
        if (provider != null) {
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        if (export != null && !export) {   // @1
            return;
        }

        if (delay != null && delay > 0) {    // @2
            delayExportExecutor.schedule(new Runnable() {
                @Override
                public void run() {
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
            doExport();    //@3
        }
    }

   代碼@1:判斷是否暴露服務,由dubbo:service export="true|false"來指定。
   代碼@2:若是啓用了delay機制,若是delay大於0,表示延遲多少毫秒後暴露服務,使用ScheduledExecutorService延遲調度,最終調用doExport方法。
   代碼@3:執行具體的暴露邏輯doExport,須要你們留意:delay=-1的處理邏輯(基於Spring事件機制觸發)。

1.2 源碼分析ServiceConfig#doExport暴露服務
   調用鏈:ServiceBean#afterPropertiesSet—調用------>ServiceConfig#export------>ServiceConfig#doExport

   ServiceConfig#checkDefault

private void checkDefault() {
        if (provider == null) {
            provider = new ProviderConfig();
        }
        appendProperties(provider);
 }

   Step1:若是dubbo:servce標籤也就是ServiceBean的provider屬性爲空,調用appendProperties方法,填充默認屬性,其具體加載順序:

  1. 從系統屬性加載對應參數值,參數鍵:dubbo.provider.屬性名,System.getProperty。
  2. 加載屬性配置文件的值。屬性配置文件,可經過系統屬性:dubbo.properties.file,若是該值未配置,則默認取dubbo.properties屬性配置文件。

   ServiceConfig#doExport

if (ref instanceof GenericService) {
      interfaceClass = GenericService.class;
      if (StringUtils.isEmpty(generic)) {
           generic = Boolean.TRUE.toString();
      }
 } else {
      try {
            interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
       checkInterfaceAndMethods(interfaceClass, methods);
       checkRef();
       generic = Boolean.FALSE.toString();
 }

   Step2:校驗ref與interface屬性。若是ref是GenericService,則爲dubbo的泛化實現,而後驗證interface接口與ref引用的類型是否一致。

   ServiceConfig#doExport

if (local != null) {
      if ("true".equals(local)) {
            local = interfaceName + "Local";
      }
      Class<?> localClass;
      try {
             localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
       } catch (ClassNotFoundException e) {
            throw new IllegalStateException(e.getMessage(), e);
       }
      if (!interfaceClass.isAssignableFrom(localClass)) {
           throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
       }
 }

   Step3:dubbo:service local機制,已經廢棄,被stub屬性所替換。
   Step4:處理本地存根Stub,<dubbo:service 的stub屬性,能夠設置爲true,此時Stub的類名爲:interface+Stub,stub也能夠指定自定義的全類名。本地存根說明如圖所示(Dubbo官方文檔)
這裏寫圖片描述
   ServiceConfig#doExport

checkApplication();
checkRegistry();
checkProtocol();
appendProperties(this);

   Step5:校驗ServiceBean的application、registry、protocol是否爲空,並從系統屬性(優先)、資源文件中填充其屬性。
   系統屬性、資源文件屬性的配置以下:
   application dubbo.application.屬性名,例如 dubbo.application.name
   registry dubbo.registry.屬性名,例如 dubbo.registry.address
   protocol dubbo.protocol.屬性名,例如 dubbo.protocol.port
   service dubbo.service.屬性名,例如 dubbo.service.stub

   ServiceConfig#doExport

checkStubAndMock(interfaceClass);

   Step6:校驗stub、mock類的合理性,是不是interface的實現類。

   ServiceConfig#doExport

doExportUrls();

   Step7:執行doExportUrls()方法暴露服務,接下來會重點分析該方法。

   ServiceConfig#doExport

ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), this, ref);
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);

   Step8:將服務提供者信息註冊到ApplicationModel實例中。

1.3 源碼分析ServiceConfig#doExportUrls暴露服務具體實現邏輯
   調用鏈:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport

private void doExportUrls() {
        List<URL> registryURLs = loadRegistries(true);  // @1
        for (ProtocolConfig protocolConfig : protocols) {
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);    // @2
        }
 }

   代碼@1:首先遍歷ServiceBean的List< RegistryConfig> registries(全部註冊中心的配置信息),而後將地址封裝成URL對象,關於註冊中心的全部配置屬性,最終轉換成url的屬性(?屬性名=屬性值),loadRegistries(true),參數的意思:true,表明服務提供者,false:表明服務消費者,若是是服務提供者,則檢測註冊中心的配置,若是配置了register=「false」,則忽略該地址,若是是服務消費者,並配置了subscribe="false"則表示不從該註冊中心訂閱服務,故也不返回,一個註冊中心URL示例:
registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&pid=7072&qos.port=22222&registry=zookeeper&timestamp=1527308268041
   代碼@2:而後遍歷配置的全部協議,根據每一個協議,向註冊中心暴露服務,接下來重點分析doExportUrlsFor1Protocol方法的實現細節。

1.4 源碼分析doExportUrlsFor1Protocol
   調用鏈:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol
   ServiceConfig#doExportUrlsFor1Protocol

String name = protocolConfig.getName();
if (name == null || name.length() == 0) {
     name = "dubbo";
}
Map<String, String> map = new HashMap<String, String>();
map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
map.put(Constants.DUBBO_VERSION_KEY, Version.getVersion());
map.put(Constants.TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
if (ConfigUtils.getPid() > 0) {
    map.put(Constants.PID_KEY, String.valueOf(ConfigUtils.getPid()));
}
appendParameters(map, application);
appendParameters(map, module);
appendParameters(map, provider, Constants.DEFAULT_KEY);
appendParameters(map, protocolConfig);
appendParameters(map, this);

   Step1:用Map存儲該協議的全部配置參數,包括協議名稱、dubbo版本、當前系統時間戳、進程ID、application配置、module配置、默認服務提供者參數(ProviderConfig)、協議配置、服務提供Dubbo:service的屬性。

   ServiceConfig#doExportUrlsFor1Protocol

if (methods != null && !methods.isEmpty()) {
            for (MethodConfig method : methods) {
                appendParameters(map, method, method.getName());
                String retryKey = method.getName() + ".retry";
                if (map.containsKey(retryKey)) {
                    String retryValue = map.remove(retryKey);
                    if ("false".equals(retryValue)) {
                        map.put(method.getName() + ".retries", "0");
                    }
                }
                List<ArgumentConfig> arguments = method.getArguments();
                if (arguments != null && !arguments.isEmpty()) {
                    for (ArgumentConfig argument : arguments) {
                        // convert argument type
                        if (argument.getType() != null && argument.getType().length() > 0) {
                            Method[] methods = interfaceClass.getMethods();
                            // visit all methods
                            if (methods != null && methods.length > 0) {
                                for (int i = 0; i < methods.length; i++) {
                                    String methodName = methods[i].getName();
                                    // target the method, and get its signature
                                    if (methodName.equals(method.getName())) {
                                        Class<?>[] argtypes = methods[i].getParameterTypes();
                                        // one callback in the method
                                        if (argument.getIndex() != -1) {
                                            if (argtypes[argument.getIndex()].getName().equals(argument.getType())) {
                                                appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                                            } else {
                                                throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", type:" + 
                                                      argument.getType());
                                            }
                                        } else {
                                            // multiple callbacks in the method
                                            for (int j = 0; j < argtypes.length; j++) {
                                                Class<?> argclazz = argtypes[j];
                                                if (argclazz.getName().equals(argument.getType())) {
                                                    appendParameters(map, argument, method.getName() + "." + j);
                                                    if (argument.getIndex() != -1 && argument.getIndex() != j) {
                                                        throw new IllegalArgumentException("argument config error : the index attribute and type attribute not match :index :" + argument.getIndex() + ", 
                                                             type:" + argument.getType());
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else if (argument.getIndex() != -1) {
                            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
                        } else {
                            throw new IllegalArgumentException("argument config must set index or type attribute.eg: <dubbo:argument index='0' .../> or <dubbo:argument type=xxx .../>");
                        }

                    }
                }
            } // end of methods for
        }

   Step2:若是dubbo:service有dubbo:method子標籤,則dubbo:method以及其子標籤的配置屬性,都存入到Map中,屬性名稱加上對應的方法名做爲前綴。dubbo:method的子標籤dubbo:argument,其鍵爲方法名.參數序號。

   ServiceConfig#doExportUrlsFor1Protocol

if (ProtocolUtils.isGeneric(generic)) {
      map.put(Constants.GENERIC_KEY, generic);
      map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
 } else {
      String revision = Version.getVersion(interfaceClass, version);
      if (revision != null && revision.length() > 0) {
          map.put("revision", revision);
      }
      String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
      if (methods.length == 0) {
           logger.warn("NO method found in service interface " + interfaceClass.getName());
           map.put(Constants.METHODS_KEY, Constants.ANY_VALUE);
      } else {
           map.put(Constants.METHODS_KEY, StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
      }
}

   Step3:添加methods鍵值對,存放dubbo:service的全部方法名,多個方法名用,隔開,若是是泛化實現,填充genric=true,methods爲"*";

   ServiceConfig#doExportUrlsFor1Protocol

if (!ConfigUtils.isEmpty(token)) {
      if (ConfigUtils.isDefault(token)) {
            map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
       } else {
            map.put(Constants.TOKEN_KEY, token);
       }
}

   Step4:根據是否開啓令牌機制,若是開啓,設置token鍵,值爲靜態值或uuid。

   ServiceConfig#doExportUrlsFor1Protocol

if (Constants.LOCAL_PROTOCOL.equals(protocolConfig.getName())) {
       protocolConfig.setRegister(false);
       map.put("notify", "false");
}

   Step5:若是協議爲本地協議(injvm),則設置protocolConfig#register屬性爲false,表示不向註冊中心註冊服務,在map中存儲鍵爲notify,值爲false,表示當註冊中心監聽到服務提供者發送變化(服務提供者增長、服務提供者減小等事件時不通知。

   ServiceConfig#doExportUrlsFor1Protocol

// export service
String contextPath = protocolConfig.getContextpath();
if ((contextPath == null || contextPath.length() == 0) && provider != null) {
     contextPath = provider.getContextpath();
}

   Step6:設置協議的contextPath,若是未配置,默認爲/interfacename

   ServiceConfig#doExportUrlsFor1Protocol

String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
Integer port = this.findConfigedPorts(protocolConfig, name, map);

   Step7:解析服務提供者的IP地址與端口。
服務IP地址解析順序:(序號越小越優先)

  1. 系統環境變量,變量名:DUBBO_DUBBO_IP_TO_BIND
  2. 系統屬性,變量名:DUBBO_DUBBO_IP_TO_BIND
  3. 系統環境變量,變量名:DUBBO_IP_TO_BIND
  4. 系統屬性,變量名:DUBBO_IP_TO_BIND
  5. dubbo:protocol 標籤的host屬性 --》 dubbo:provider 標籤的host屬性
  6. 默認網卡IP地址,經過InetAddress.getLocalHost().getHostAddress()獲取,若是IP地址不符合要求,繼續下一個匹配。

   判斷IP地址是否符合要求的標準是:

public static boolean isInvalidLocalHost(String host) {
        return host == null
                || host.length() == 0
                || host.equalsIgnoreCase("localhost")
                || host.equals("0.0.0.0")
                || (LOCAL_IP_PATTERN.matcher(host).matches());
      }
  1. 選擇第一個可用網卡,其實現方式是創建socket,鏈接註冊中心,獲取socket的IP地址。其代碼:
Socket socket = new Socket();
        try {
              SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
              socket.connect(addr, 1000);
              hostToBind = socket.getLocalAddress().getHostAddress();
              break;
         } finally {
              try {
                      socket.close();
              } catch (Throwable e) {
              }
        }

服務提供者端口解析順序:(序號越小越優先)

  1. 系統環境變量,變量名:DUBBO_DUBBO_PORT_TO_BIND
  2. 系統屬性,變量名:DUBBO_DUBBO_PORT_TO_BIND
  3. 系統環境變量,變量名:DUBBO_PORT_TO_BIND
  4. 系統屬性,變量名DUBBO_PORT_TO_BIND
  5. dubbo:protocol標籤port屬性、dubbo:provider標籤的port屬性。
  6. 隨機選擇一個端口。

   ServiceConfig#doExportUrlsFor1Protocol

URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);

   Step8:根據協議名稱、協議host、協議端口、contextPath、相關配置屬性(application、module、provider、protocolConfig、service及其子標籤)構建服務提供者URI。
   URL運行效果圖:
這裏寫圖片描述
   以dubbo協議爲例,展現最終服務提供者的URL信息以下:dubbo://192.168.56.1:20880/com.alibaba.dubbo.demo.DemoService?anyhost=true&application=demo-provider&bind.ip=192.168.56.1&bind.port=20880&dubbo=2.0.0&generic=false&interface=com.alibaba.dubbo.demo.DemoService&methods=sayHello&pid=5916&qos.port=22222&side=provider&timestamp=1527168070857

   ServiceConfig#doExportUrlsFor1Protocol

String scope = url.getParameter(Constants.SCOPE_KEY);
// don't export when none is configured
if (!Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {
     // 接口暴露實現邏輯
}

   Step9:獲取dubbo:service標籤的scope屬性,其可選值爲none(不暴露)、local(本地)、remote(遠程),若是配置爲none,則不暴露。默認爲local。

   ServiceConfig#doExportUrlsFor1Protocol

// export to local if the config is not remote (export to remote only when config is remote)
if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {     // @1
       exportLocal(url);
}
// export to remote if the config is not local (export to local only when config is local)
if (!Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope)) {     // @2
        if (logger.isInfoEnabled()) {
              logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
         }
        if (registryURLs != null && !registryURLs.isEmpty()) {   // @3
              for (URL registryURL : registryURLs) {
                    url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY, registryURL.getParameter(Constants.DYNAMIC_KEY));  // @4
                    URL monitorUrl = loadMonitor(registryURL);       // @5
                    if (monitorUrl != null) {
                        url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());  
                    }
                    if (logger.isInfoEnabled()) {
                        logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                    }
                   Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));  // @6
                   DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);    
                   Exporter<?> exporter = protocol.export(wrapperInvoker);    // @7
                   exporters.add(exporter);
               }
         } else {
               Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
               DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
               Exporter<?> exporter = protocol.export(wrapperInvoker);
               exporters.add(exporter);
         }
}

   Step10:根據scope來暴露服務,若是scope不配置,則默認本地與遠程都會暴露,若是配置成local或remote,那就只能是二選一。
   代碼@1:若是scope不爲remote,則先在本地暴露(injvm):,具體暴露服務的具體實現,將在remote 模式中詳細分析。
   代碼@2:若是scope不爲local,則將服務暴露在遠程。
   代碼@3:remote方式,檢測當前配置的全部註冊中心,若是註冊中心不爲空,則遍歷註冊中心,將服務依次在不一樣的註冊中心進行註冊。
   代碼@4:若是dubbo:service的dynamic屬性未配置, 嘗試取dubbo:registry的dynamic屬性,該屬性的做用是否啓用動態註冊,若是設置爲false,服務註冊後,其狀態顯示爲disable,須要人工啓用,當服務不可用時,也不會自動移除,一樣須要人工處理,此屬性不要在生產環境上配置。
   代碼@5:根據註冊中心url(註冊中心url),構建監控中心的URL,若是監控中心URL不爲空,則在服務提供者URL上追加monitor,其值爲監控中心url(已編碼)。

  • 若是dubbo spring xml配置文件中沒有配置監控中心(dubbo:monitor),若是從系統屬性-Ddubbo.monitor.address,-Ddubbo.monitor.protocol構建MonitorConfig對象,不然從dubbo的properties配置文件中尋找這個兩個參數,若是沒有配置,則返回null。
  • 若是有配置,則追加相關參數,dubbo:monitor標籤只有兩個屬性:address、protocol,其次會追加interface(MonitorService)、協議等。

   代碼@6:經過動態代理機制建立Invoker,dubbo的遠程調用實現類。
這裏寫圖片描述
   Dubbo遠程調用器如何構建,這裏不詳細深刻,重點關注WrapperInvoker的url爲:registry://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D6328%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527255510215&pid=6328&qos.port=22222&registry=zookeeper&timestamp=1527255510202,這裏有兩個重點值得關注:

  • path屬性:com.alibaba.dubbo.registry.RegistryService,註冊中心也相似於服務提供者。
  • export屬性:值爲服務提供者的URL,爲何須要關注這個URL呢?請看代碼@7,protocol屬性爲Protocol$Adaptive,Dubbo在加載組件實現類時採用SPI(插件機制,有關於插件機制,在該專題後續文章將重點分析),在這裏咱們只須要知道,根據URL冒號以前的協議名將會調用相應的方法。
    這裏寫圖片描述
       其映射關係(列出與服務啓動相關協議實現類):
    dubbo=com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol //文件位於dubbo-rpc-dubbo/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
    registry=com.alibaba.dubbo.registry.integration.RegistryProtocol //文件位於dubbo-registry-api/src/main/resources/META-INF/dubbo/internal/com.alibaba.dubbo.rpc.Protocol
       代碼@7:根據代碼@6的分析,將調用RegistryProtocol#export方法。

1.5 源碼分析RegistryProtocol#export方法
   調用鏈:ServiceBean#afterPropertiesSet------>ServiceConfig#export------>ServiceConfig#doExport------>ServiceConfig#doExportUrlsFor1Protocol------>RegistryProtocol#export

   RegistryProtocol#export

@Override
    public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);   // @1

        URL registryUrl = getRegistryUrl(originInvoker);       // @2

        //registry provider
        final Registry registry = getRegistry(originInvoker);                          // @3
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);     // @4start

        //to judge to delay publish whether or not
        boolean register = registedProviderUrl.getParameter("register", true);  

        ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registedProviderUrl);

        if (register) {  
            register(registryUrl, registedProviderUrl);      // @4 end
            ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
        }

        // Subscribe the override data
        // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);          // @5 start
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);            // @5 end
        //Ensure that a new exporter instance is returned every time export
        return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registedProviderUrl);
    }

   代碼@1:啓動服務提供者服務,監聽指定端口,準備服務消費者的請求,這裏其實就是從WrapperInvoker中的url(註冊中心url)中提取export屬性,描述服務提供者的url,而後啓動服務提供者。
這裏寫圖片描述
   從上圖中,能夠看出,將調用DubboProtocol#export完成dubbo服務的啓動,利用netty構建一個微型服務端,監聽端口,準備接受服務消費者的網絡請求,本節旨在梳理其啓動流程,具體實現細節,將在後續章節中詳解,這裏咱們只要知道,< dubbo:protocol name=「dubbo」 port=「20880」 />,會再這次監聽該端口,而後將dubbo:service的服務handler加入到命令處理器中,當有消息消費者鏈接該端口時,經過網絡解包,將須要調用的服務和參數等信息解析處理後,轉交給對應的服務實現類處理便可。
   代碼@2:獲取真實註冊中心的URL,例如zookeeper註冊中心的URL:zookeeper://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&export=dubbo%3A%2F%2F192.168.56.1%3A20880%2Fcom.alibaba.dubbo.demo.DemoService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26bind.ip%3D192.168.56.1%26bind.port%3D20880%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.DemoService%26methods%3DsayHello%26pid%3D10252%26qos.port%3D22222%26side%3Dprovider%26timestamp%3D1527263060882&pid=10252&qos.port=22222&timestamp=1527263060867
   代碼@3:根據註冊中心URL,從註冊中心工廠中獲取指定的註冊中心實現類:zookeeper註冊中心的實現類爲:ZookeeperRegistry
   代碼@4:獲取服務提供者URL中的register屬性,若是爲true,則調用註冊中心的ZookeeperRegistry#register方法向註冊中心註冊服務(實際由其父類FailbackRegistry實現)。
   代碼@5:服務提供者向註冊中心訂閱本身,主要是爲了服務提供者URL發送變化後從新暴露服務,固然,會將dubbo:reference的check屬性設置爲false。

   到這裏就對文章開頭提到的問題1,問題2作了一個解答,其與註冊中心的心跳機制等將在後續章節中詳細分析。

   文字看起來可能不是很直觀,現整理一下Dubbo服務提供者啓動流程圖以下:
這裏寫圖片描述

   本文重點梳理了Dubbo服務提供者啓動流程,其中Dubbo服務提供者在指定端口監聽服務的啓動流程將在下一節中詳細分析。