1. 程式人生 > >Dubbo配置屬性初始化詳解

Dubbo配置屬性初始化詳解

        在前面的文章中,我們講解了Dubbo是如何建立Provider Bean的(Dubbo之provider bean註冊詳解),其本質就是為每一個使用<dubbo:service/>宣告的介面都使用一個ServiceBean進行封裝。本文主要講解ServiceBean是如何為每一個provider bean初始化其預設配置的,以便為後續的服務暴露做準備的

1. ApplicationContext事件啟動監聽

        ServiceBean暴露服務的入口方法是ServiceBean.export()方法,而該方法主要有兩個地方在呼叫:

  • ServiceBean.afterPropertySet()
    獲取配置屬性之後呼叫;
  • ServiceBean.onApplicationEvent()中監聽ApplicationContext啟動完成事件後呼叫。

        在預設情況下,Dubbo是會通過第二種方式進行服務暴露的,因為這樣可以保證Dubbo的啟動是在Spring啟動之後,也就給予我們一種可以在暴露過程中使用已經完全初始化的Spring服務的功能。至於第一種情況和第二種情況的區別主要在於Dubbo會檢測Spring是否支援事件的監聽機制,如果支援,則使用第二種方式,不支援則使用第一種機制。具體的檢測方式是在ServiceBean.setApplicationContext()方法中進行的:

@Override
public void setApplicationContext(ApplicationContext applicationContext) {
  this.applicationContext = applicationContext;
  // 將ApplicationContext設定到SpringExtensionFactory中,這樣就可以使得Dubbo的SPI機制
  // 得以使用Spring容器中的相關服務功能
  SpringExtensionFactory.addApplicationContext(applicationContext);
  // 檢測是否支援事件監聽機制,如果支援,則將當前ServiceBean當做一個事件註冊到Spring容器中
  supportedApplicationListener = addApplicationListener(applicationContext, this);
}

public static boolean addApplicationListener(ApplicationContext applicationContext,
      ApplicationListener listener) {
  try {
    // 獲取ApplicationContext物件中的addApplicationListener()方法,
    // 並且通過反射呼叫該方法,從而註冊監聽事件,如果當前ApplicationContext沒有該方法,
    // 那麼這裡就會丟擲異常
    Method method = applicationContext.getClass()
      .getMethod("addApplicationListener", ApplicationListener.class);
    method.invoke(applicationContext, listener);
    return true;
  } catch (Throwable t) {
    if (applicationContext instanceof AbstractApplicationContext) {
      try {
        // 如果ApplicationContext物件是AbstractApplicationContext型別的,則通過
        // 反射呼叫其addListener()方法,新增監聽的事件。
        Method method = AbstractApplicationContext.class
          .getDeclaredMethod("addListener", ApplicationListener.class);
        if (!method.isAccessible()) {
          method.setAccessible(true);
        }
        method.invoke(applicationContext, listener);
        return true;
      } catch (Throwable t2) {
        // ignore
      }
    }
  }
  
  // 如果上述兩種方式都不支援,則表示當前ApplicationContext不支援事件監聽機制
  return false;
}

        關於Dubbo為何使用這種方式來檢查是否支援事件監聽機制的原因有兩點:

  • 上面的addApplicationListener()方法是ApplicationContext介面的子介面ConfigurableApplicationContext的一個方法,因而如果使用者使用了自定義的ApplicationContext,那麼其就不一定支援事件監聽機制;
  • catch語句中的第二種新增方式主要是因為在舊版本中,事件的新增是通過addListener()方法進行的,在最新的版本中已經移除了該方法。

        由於一般使用者是不會修改預設使用的ApplicationContext的,因而大多數情況下,Dubbo還是使用事件監聽機制來匯出服務。

2. afterPropertySet()方式配置屬性

        Dubbo的服務配置,不僅僅可以使用<dubbo:service/>等配置標籤的方式來宣告所使用的application、registry和protocol,其還可以使用宣告Spring bean的方式來進行。具體的步驟就是通過ServiceBean.afterPropertySet()方法進行讀取的,其程式碼如下:

@Override
public void afterPropertiesSet() throws Exception {
  // 首先判斷當前ServiceBean中是否已經設定了ProviderConfig物件,如果不存在,
  // 則嘗試讀取Spring容器中配置的ProviderConfig物件
  if (getProvider() == null) {
    Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
           ProviderConfig.class, false, false);
    if (providerConfigMap != null && providerConfigMap.size() > 0) {
      Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null
        : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
              ProtocolConfig.class, false, false);
      // 上面的程式碼中首先從容器中獲取了ProviderConfig,然後獲取了ProtocolConfig,在下面的分支中,
      // 首先會判斷讀取到的ProtocolConfig是空的,並且得到的ProviderConfig的數量大於1,這說明
      // 其是多協議支援的Provider,這個時候,就會將ProviderConfig轉換為ProtocolConfig設定
      // 到當前ServiceBean中,需要注意的是,下面的setProviders()方法就是進行這個轉換過程的,
      // 其並不會為當前ServiceBean的ProviderConfig設定任何資料。而且每個ServiceBean中也只會有
      // 一個ProviderConfig
      if (CollectionUtils.isEmptyMap(protocolConfigMap)
          && providerConfigMap.size() > 1) {
        List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
        for (ProviderConfig config : providerConfigMap.values()) {
          if (config.isDefault() != null && config.isDefault()) {
            providerConfigs.add(config);
          }
        }
        if (!providerConfigs.isEmpty()) {
          setProviders(providerConfigs);	// 將ProviderConfig轉換為ProtocolConfig儲存起來
        }
      } else {
        // 這個分支說明容器中是存在ProtocolConfig的配置,或者得到的ProviderConfig只有一個,
        // 那麼這裡就會將該ProviderConfig設定到ServiceBean中
        ProviderConfig providerConfig = null;
        for (ProviderConfig config : providerConfigMap.values()) {
          if (config.isDefault() == null || config.isDefault()) {
            if (providerConfig != null) {
              throw new IllegalStateException(
                "Duplicate provider configs: " + providerConfig + " and " + config);
            }
            providerConfig = config;
          }
        }
        if (providerConfig != null) {
          setProvider(providerConfig);
        }
      }
    }
  }
  
  // 如果沒有配置ApplicationConfig,並且也無法從ProviderConfig中獲取到ApplicationConfig,
  // 那麼就到Spring容器中查詢ApplicationConfig物件
  if (getApplication() == null
      && (getProvider() == null || getProvider().getApplication() == null)) {
    Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
            ApplicationConfig.class, false, false);
    if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
      // 從Spring容器中獲取ApplicationConfig,並且將該物件設定到ServiceBean中
      ApplicationConfig applicationConfig = null;
      for (ApplicationConfig config : applicationConfigMap.values()) {
        if (applicationConfig != null) {
          throw new IllegalStateException(
            "Duplicate application configs: " + applicationConfig + " and " + config);
        }
        applicationConfig = config;
      }
      if (applicationConfig != null) {
        setApplication(applicationConfig);
      }
    }
  }
  
  // 如果沒有配置ModuleConfig,並且也無法從ProviderConfig中獲取到ModuleConfig,
  // 那麼就到Spring容器中查詢ModuleConfig物件
  if (getModule() == null
      && (getProvider() == null || getProvider().getModule() == null)) {
    Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
            ModuleConfig.class, false, false);
    if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
      // 從Spring容器中獲取ModuleConfig,並且將該物件設定到ServiceBean中
      ModuleConfig moduleConfig = null;
      for (ModuleConfig config : moduleConfigMap.values()) {
        if (config.isDefault() == null || config.isDefault()) {
          if (moduleConfig != null) {
            throw new IllegalStateException(
              "Duplicate module configs: " + moduleConfig + " and " + config);
          }
          moduleConfig = config;
        }
      }
      if (moduleConfig != null) {
        setModule(moduleConfig);
      }
    }
  }

  // 如果沒有配置registryIds屬性,則從ApplicationConfig中讀取該屬性,然後從ProviderConfig
  // 中讀取該屬性。這裡存在一個優先順序的關係,如果ApplicationConfig和ProviderConfig中都存在
  // 該屬性,那麼最終將會以ProviderConfig中的為準。
  if (StringUtils.isEmpty(getRegistryIds())) {
    if (getApplication() != null 
        && StringUtils.isNotEmpty(getApplication().getRegistryIds())) {
      setRegistryIds(getApplication().getRegistryIds());
    }
    if (getProvider() != null && StringUtils.isNotEmpty(getProvider().getRegistryIds())) {
      setRegistryIds(getProvider().getRegistryIds());
    }
  }

  // 如果ProviderConfig和ApplicationConfig都沒有指定RegistryConfig,那麼就從Spring容器中
  // 讀取,然後判斷是否配置了registryIds,如果配置了,則通過registryIds獲取RegistryConfig,
  // 如果沒有配置,則將得到的所有RegistryConfig都設定到ServiceBean中
  if ((CollectionUtils.isEmpty(getRegistries())) && (getProvider() == null 
          || CollectionUtils.isEmpty(getProvider().getRegistries()))
          && (getApplication() == null 
          || CollectionUtils.isEmpty(getApplication().getRegistries()))) {
    Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
            RegistryConfig.class, false, false);
    if (CollectionUtils.isNotEmptyMap(registryConfigMap)) {
      List<RegistryConfig> registryConfigs = new ArrayList<>();
      // 如果配置了registryIds,則只獲取指定的RegistryConfig
      if (StringUtils.isNotEmpty(registryIds)) {
        Arrays.stream(Constants.COMMA_SPLIT_PATTERN.split(registryIds)).forEach(id -> {
          if (registryConfigMap.containsKey(id)) {
            registryConfigs.add(registryConfigMap.get(id));
          }
        });
      }

      // 如果通過registryIds沒有獲取到RegistryConfig,則將所有的RegistryConfig都新增到ServiceBean中
      if (registryConfigs.isEmpty()) {
        for (RegistryConfig config : registryConfigMap.values()) {
          if (StringUtils.isEmpty(registryIds)) {
            registryConfigs.add(config);
          }
        }
      }
      if (!registryConfigs.isEmpty()) {
        super.setRegistries(registryConfigs);
      }
    }
  }
  
  // 如果配置的MetadataConfig為空,則從Spring容器中獲取一個MetadataConfig,
  // 將其設定到ServiceBean中,如果從Spring容器中獲取到了多個,則丟擲異常
  if (getMetadataReportConfig() == null) {
    Map<String, MetadataReportConfig> metadataReportConfigMap = applicationContext == null 
      ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
           MetadataReportConfig.class, false, false);
    if (metadataReportConfigMap != null && metadataReportConfigMap.size() == 1) {
      super.setMetadataReportConfig(metadataReportConfigMap.values().iterator().next());
    } else if (metadataReportConfigMap != null && metadataReportConfigMap.size() > 1) {
      throw new IllegalStateException(
        "Multiple MetadataReport configs: " + metadataReportConfigMap);
    }
  }

  // 如果配置的ConfigCenter為空,則從Spring容器中獲取一個ConfigCenter,
  // 將其設定到ServiceBean中,如果從Spring容器中獲取到了多個,則丟擲異常
  if (getConfigCenter() == null) {
    Map<String, ConfigCenterConfig> configenterMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
            ConfigCenterConfig.class, false, false);
    if (configenterMap != null && configenterMap.size() == 1) {
      super.setConfigCenter(configenterMap.values().iterator().next());
    } else if (configenterMap != null && configenterMap.size() > 1) {
      throw new IllegalStateException("Multiple ConfigCenter found:" + configenterMap);
    }
  }

  // 如果配置的MonitorConfig為空,並且從ProviderConfig和ApplicationConfig中都無法獲取到
  // MonitorConfig,則從Spring容器中讀取,需要注意的是,配置的MonitorConfig只能存在一個預設的
  if (getMonitor() == null
      && (getProvider() == null || getProvider().getMonitor() == null)
      && (getApplication() == null || getApplication().getMonitor() == null)) {
    Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
            MonitorConfig.class, false, false);
    if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
      MonitorConfig monitorConfig = null;
      for (MonitorConfig config : monitorConfigMap.values()) {
        if (config.isDefault() == null || config.isDefault()) {
          if (monitorConfig != null) {
            throw new IllegalStateException(
              "Duplicate monitor configs: " + monitorConfig + " and " + config);
          }
          monitorConfig = config;
        }
      }
      if (monitorConfig != null) {
        setMonitor(monitorConfig);
      }
    }
  }

  // 設定protocolIds屬性
  if (StringUtils.isEmpty(getProtocolIds())) {
    if (getProvider() != null && StringUtils.isNotEmpty(getProvider().getProtocolIds())) {
      setProtocolIds(getProvider().getProtocolIds());
    }
  }

  // 如果配置的ProtocolConfig為空,則從Spring容器中讀取,然後會判斷當前Provider是否配置了protocolIds
  // 屬性,如果配置了,則只讀取該Ids指定的ProtocolConfig,如果沒有配置,則將所有的ProtocolConfig都
  // 設定到ServiceBean中
  if (CollectionUtils.isEmpty(getProtocols())
      && (getProvider() == null || CollectionUtils.isEmpty(getProvider().getProtocols()))) {
    Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null
      : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, 
            ProtocolConfig.class, false, false);
    if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
      List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
      // 通過protocolIds屬性來獲取配置的ProtocolConfig
      if (StringUtils.isNotEmpty(getProtocolIds())) {
        Arrays.stream(Constants.COMMA_SPLIT_PATTERN.split(getProtocolIds()))
          .forEach(id -> {
            if (protocolConfigMap.containsKey(id)) {
              protocolConfigs.add(protocolConfigMap.get(id));
            }
          });
      }

      // 如果獲取到的ProtocolConfig為空,則將所有的ProtocolConfig都新增到ServiceBean中
      if (protocolConfigs.isEmpty()) {
        for (ProtocolConfig config : protocolConfigMap.values()) {
          if (StringUtils.isEmpty(protocolIds)) {
            protocolConfigs.add(config);
          }
        }
      }

      if (!protocolConfigs.isEmpty()) {
        super.setProtocols(protocolConfigs);
      }
    }
  }
  
  // 設定path屬性,預設為介面的全限定名
  if (StringUtils.isEmpty(getPath())) {
    if (StringUtils.isNotEmpty(beanName)
        && StringUtils.isNotEmpty(getInterface())
        && beanName.startsWith(getInterface())) {
      setPath(beanName);
    }
  }
  
  // 這裡會判斷是否支援事件監聽機制,如果不支援,則在這裡匯出Dubbo服務
  if (!supportedApplicationListener) {
    export();
  }
}

        上面的程式碼中,主要邏輯就是判斷配置檔案中是否配置了ApplicationConfig、ProviderConfig等物件對應的標籤,如果沒有配置,則會從Spring容器中讀取這些配置,然後將其設定到當前ServiceBean中。最後會判斷當前是否支援事件監聽機制,如果不支援,就會在最後通過呼叫export()方法匯出Dubbo服務。

3. 預設屬性配置

        上述屬性讀取完成之後,最終會呼叫ServiceBean.export()方法匯出服務,但是在匯出服務之前,會根據各個層級的配置,來根據優先順序配置各個屬性值。這裡我們繼續閱讀export()方法的原始碼:

public synchronized void export() {
  // 檢查各個預設屬性的配置,並且按照優先順序進行覆蓋
  checkAndUpdateSubConfigs();

  // 檢查是否應該匯出服務,如果當前正在匯出或者已經取消了匯出,那麼就會在這裡被攔截
  if (!shouldExport()) {
    return;
  }

  // 這裡主要是用於延遲匯出的,最終還是會交由doExport()方法匯出服務
  if (shouldDelay()) {
    delayExportExecutor.schedule(this::doExport, delay, TimeUnit.MILLISECONDS);
  } else {
    doExport();
  }
}

        上面的匯出過程,主要分為兩個:a. 檢查屬性的配置,並且根據配置的優先順序進行屬性的覆蓋;b. 呼叫doExportUrl()匯出服務。我們首先來看checkAndUpdateSubConfigs()是如何進行屬性的優先順序檢查的:

public void checkAndUpdateSubConfigs() {
  // 為ServiceBean設定預設屬性,比如當前配置了ProviderConfig,但是沒有配置RegistryConfig,那麼
  // 有可能ProviderConfig中是配置了其所要進行的註冊資訊的,此時就會通過ProviderConfig獲取
  // RegistryConfig物件,從而保證RegistryConfig有值。需要說明的是,如果ProviderConfig中
  // 也還是沒有配置RegistryConfig,那麼RegistryConfig還是會為空
  completeCompoundConfigs();
  // 設定ConfigCenterConfig的相關屬性,這裡本質上實現的工作就是依次通過SystemConfiguration 
  // -> ExternalConfiguration -> AppExternalConfiguration -> AbstractConfig 
  // -> PropertiesConfiguration的優先順序來讀取屬性配置,然後將其設定到ConfigCenterConfig中
  startConfigCenter();
  // 檢查是否有ProviderConfig的配置,如果不存在,則建立一個
  checkDefault();
  // 檢查是否存在ApplicationConfig的配置,如果不存在,則新建一個
  checkApplication();
  // 檢查是否存在RegistryConfig的配置,如果不存在,則新建一個
  checkRegistry();
  // 檢查是否存在ProtocolConfig的配置,如果不存在,則新建一個
  checkProtocol();
  // 依次對新建的ProviderConfig,ApplicationConfig,RegistryConfig和ProtocolConfig設定屬性,
  // 設定的方式就是通過讀取外部配置的屬性值,然後通過呼叫這些Config物件的setter方法將其設定到各個
  // Config類中
  this.refresh();
  // 檢查是否存在MetadataReportConfig,如果不存在,則新建一個,並且呼叫其refresh()方法設定屬性
  checkMetadataReport();

  if (StringUtils.isEmpty(interfaceName)) {
    throw new IllegalStateException("<dubbo:service interface=\"\" /> " 
        + "interface not allow null!");
  }

  // 如果當前介面型別是泛華型別,則設定generic屬性為true,並且設定interfaceClass為GenericService
  if (ref instanceof GenericService) {
    interfaceClass = GenericService.class;
    if (StringUtils.isEmpty(generic)) {
      generic = Boolean.TRUE.toString();
    }
  } else {
    try {
      // 如果當前配置的class是普通的class,則通過反射讀取該class檔案
      interfaceClass = Class.forName(interfaceName, true, 
          Thread.currentThread().getContextClassLoader());
    } catch (ClassNotFoundException e) {
      throw new IllegalStateException(e.getMessage(), e);
    }
    // 這裡主要是檢查在當前介面中配置的方法名和引數等資訊與真實讀取到的class檔案的方法名和引數資訊是否匹配
    checkInterfaceAndMethods(interfaceClass, methods);
    // 這裡主要是檢查ref屬性指向的物件型別是否為所設定的介面的一個實現類
    checkRef();
    // 此時generic為false,因為其為普通介面
    generic = Boolean.FALSE.toString();
  }
  
  // 如果配置了local屬性,那麼就會在當前classpath中查詢目標介面名加上Local字尾的實現類,
  // 這個實現類將作為需要暴露的目標服務的一個代理類,這種使用方式已經被廢棄,取而代之的是下面的Stub方式
  if (local != null) {
    if ("true".equals(local)) {
      local = interfaceName + "Local";
    }
    Class<?> localClass;
    try {
      // 載入Local代理
      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);
    }
  }
  
  // 如果配置了stub屬性,那麼就會在classpath中查詢目標介面名加上Stub字尾的實現類,
  // 這個類將會被作為代理類在客戶端使用,其可以對需要代理的服務進行一些自定義的邏輯,比如進行快取等
  if (stub != null) {
    if ("true".equals(stub)) {
      stub = interfaceName + "Stub";
    }
    Class<?> stubClass;
    try {
      // 載入Stub代理類
      stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
    } catch (ClassNotFoundException e) {
      throw new IllegalStateException(e.getMessage(), e);
    }
    
    // 如果載入的代理類不是目標介面的一個實現類,則丟擲異常
    if (!interfaceClass.isAssignableFrom(stubClass)) {
      throw new IllegalStateException(
        "The stub implementation class " + stubClass.getName() + " not implement interface "
        + interfaceName);
    }
  }
  
  // 由於Local和Stub類都是代理類,因而這裡會檢查載入的這兩個類是否存在以目標介面為引數的建構函式,
  // 如果不存在,則丟擲異常
  checkStubAndLocal(interfaceClass);
  // 檢查是否配置了mock屬性,如果配置了,則檢查該屬性是否符合規範。mock屬性的作用主要是在目標服務介面丟擲
  // 異常時,將會使用mock屬性所指定的方式來返回虛擬值
  checkMock(interfaceClass);
}

        這裡checkAndUpdateSubConfigs()方法首先對各個Config類的屬性進行覆蓋,覆蓋方式是根據優先順序來讀取系統屬性,外部配置等屬性值,然後將這些屬性值依次設定到各個Config類中;然後就是處理Local和Stub型別的代理類,並且檢查這些類是否符合規範;最後就是檢查mock屬性的值是否符合規範。下面我們來看一下doExport()方法是如何匯出目標服務的:

protected synchronized void doExport() {
  if (unexported) {
    throw new IllegalStateException(
      "The service " + interfaceClass.getName() + " has already unexported!");
  }
  if (exported) {
    return;
  }
  exported = true;

  if (StringUtils.isEmpty(path)) {
    path = interfaceName;
  }
  
  // 匯出服務
  doExportUrls();
}

private void doExportUrls() {
  // 獲取各個註冊中心的配置,並且將其轉換為URL物件
  List<URL> registryURLs = loadRegistries(true);
  for (ProtocolConfig protocolConfig : protocols) {
    // 獲取當前服務的key,其格式為group/interface/version,這個key是用於標識當前服務的一個唯一鍵
    String pathKey = URL.buildKey(getContextPath(protocolConfig).map(p -> p + "/" 
        + path).orElse(path), group, version);
    // 根據服務key,interfaceClass和目標bean構建一個ProviderModel物件,這個ProviderModel
    // 是對應於Provider的一個封裝
    ProviderModel providerModel = new ProviderModel(pathKey, ref, interfaceClass);
    // 將服務key與ProviderModel關聯起來
    ApplicationModel.initProviderModel(pathKey, providerModel);
    // 匯出服務
    doExportUrlsFor1Protocol(protocolConfig, registryURLs);
  }
}

        在doExportUrls()方法中,主要是依次對各個ProtocolConfig都提供當前Provider的暴露功能,這裡的ProtocolConfig其實配置的就是傳輸使用的協議方式,比如netty或者mina等。我們繼續閱讀doExportUrlsFor1Protocol()方法的原始碼:

private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
  // 獲取協議名稱,預設為dubbo
  String name = protocolConfig.getName();
  if (StringUtils.isEmpty(name)) {
    name = Constants.DUBBO;
  }

  // 這裡的map中儲存的是暴露服務時最終將要使用的屬性,其會被轉換為一個URL物件,而URL物件是Dubbo在整個
  // 服務暴露過程中所使用的一個傳遞引數的物件,因而其作用非常重要。在下面的呼叫中,會依次對
  // ApplicationConfig,ModuleConfig和ProviderConfig來執行appendParameters()方法,這個方法的
  // 主要作用是通過這些Config類的getter方法獲取其屬性值,然後將其屬性值設定到map中,如果指定了屬性字首,
  // 那麼map中使用的key就是"字首.屬性名"的形式。從這裡就可以看出,對於屬性的優先順序,是以
  // ServiceConfig > ProtocolConfig > ProviderConfig > ModuleConfig > ApplicationConfig
  // 的順序排列的
  Map<String, String> map = new HashMap<String, String>();
  map.put(Constants.SIDE_KEY, Constants.PROVIDER_SIDE);
  appendRuntimeParameters(map);
  appendParameters(map, application);
  appendParameters(map, module);
  appendParameters(map, provider, Constants.DEFAULT_KEY);
  appendParameters(map, protocolConfig);
  appendParameters(map, this);

  // 這段程式碼的主要作用是判斷配置中對目標介面配置的方法以及方法引數是否與真實的介面的方法和引數匹配
  if (CollectionUtils.isNotEmpty(methods)) {
    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 (CollectionUtils.isNotEmpty(arguments)) {
        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();
                // 判斷配置的方法名與真實的方法名是否一致,如果一致,則繼續進行方法引數的判斷
                if (methodName.equals(method.getName())) {
                  // 獲取真實介面的方法引數資訊
                  Class<?>[] argtypes = methods[i].getParameterTypes();
                  // 如果配置中的引數的index屬性有值,說明其指定了該引數在對應的方法中的位置,
                  // 此時可以直接判斷呢真實的引數與該配置的引數型別是否一致
                  if (argument.getIndex() != -1) {
                    if (argtypes[argument.getIndex()].getName()
                        .equals(argument.getType())) {
                      // 方法和引數都匹配,則將方法引數的配置資訊設定到map中
                      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 {
                    // 這個分支是方法引數中沒有配置index屬性,這種情況下直接遍歷方法的所有引數,
                    // 判斷其與配置的引數型別是否一致
                    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) {
            // 這個分支是配置的引數型別為空,此時就檢查其index屬性,不為空的時候才進行處理
            appendParameters(map, argument, method.getName() + "." + argument.getIndex());
          } else {
            // 當配置的引數型別和index都為空的情況下,丟擲異常
            throw new IllegalArgumentException(
              "Argument config must set index or type attribute.eg: <dubbo:argument " 
              + "index='0' .../> or <dubbo:argument type=xxx .../>");
          }

        }
      }
    }
  }

  // 判斷目標類是否為泛化型別,如果是,則設定與泛化相關的屬性值
  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);
    }

    // 這裡的Wrapper類是一個封裝類,其作用主要是對需要暴露的介面進行一個統一的封裝,
    // 其形式非常類似於反射,但是其與反射不同,這裡的Wrapper會為目標介面動態生成子類位元組碼,
    // 然後通過javassist來編譯生成的位元組碼,最後通過反射獲取該類的物件。
    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)), ","));
    }
  }
  
  // 為當前provider設定一個唯一的id
  if (!ConfigUtils.isEmpty(token)) {
    if (ConfigUtils.isDefault(token)) {
      map.put(Constants.TOKEN_KEY, UUID.randomUUID().toString());
    } else {
      map.put(Constants.TOKEN_KEY, token);
    }
  }

  // 獲取配置的需要匯出的ip和埠號資訊
  String host = this.findConfigedHosts(protocolConfig, registryURLs, map);
  Integer port = this.findConfigedPorts(protocolConfig, name, map);
  // 這裡,就將所有得到的配置屬性和服務資訊構建為了一個url物件,該物件將在後面暴露服務中
  // 起到傳遞引數的作用
  URL url = new URL(name, host, port, getContextPath(protocolConfig).map(p -> p + "/" 
      + path).orElse(path), map);

  // 這裡主要是對生成的URL物件的屬性進行一些覆蓋操作
  if (ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
      .hasExtension(url.getProtocol())) {
    url = ExtensionLoader.getExtensionLoader(ConfiguratorFactory.class)
      .getExtension(url.getProtocol()).getConfigurator(url).configure(url);
  }

  // 下面主要是進行暴露服務的工作,其主要分為兩個部分:injvm和registry。injvm指的是將服務暴露到
  // 當前jvm中,在當前jvm中可以進行相關呼叫。registry指的是根據相關的註冊中心配置,如zookeeper或redis,
  // 將服務資訊暴露到對應的註冊中心中。需要注意的是,在暴露到註冊中心的同時,也會在本地進行一次暴露,通過
  // 這種方式,我們就可以在本地進行服務直連,而不用到註冊中心拉取服務
  String scope = url.getParameter(Constants.SCOPE_KEY);
  // 獲取scope屬性值,如果屬性值不為none,則開始進行暴露服務的工作
  if (!Constants.SCOPE_NONE.equalsIgnoreCase(scope)) {
    if (!Constants.SCOPE_REMOTE.equalsIgnoreCase(scope)) {
      // 如果scope不為remote,則會將服務暴露到當前的jvm中
      exportLocal(url);
    }

    // 如果scope不為local,則會將服務暴露到配置的註冊中心中
    if (!Constants.SCOPE_LOCAL.equalsIgnoreCase(scope)) {
      if (logger.isInfoEnabled()) {
        logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
      }
      if (CollectionUtils.isNotEmpty(registryURLs)) {
        // 依次遍歷各個註冊中心的配置,將當前服務註冊到註冊中心
        for (URL registryURL : registryURLs) {
          url = url.addParameterIfAbsent(Constants.DYNAMIC_KEY,
              registryURL.getParameter(Constants.DYNAMIC_KEY));
          
          // 獲取監控中心的相關配置
          URL monitorUrl = loadMonitor(registryURL);
          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);
          }

          // 獲取生成動態代理的方式,如javassist或jdk
          String proxy = url.getParameter(Constants.PROXY_KEY);
          if (StringUtils.isNotEmpty(proxy)) {
            registryURL = registryURL.addParameter(Constants.PROXY_KEY, proxy);
          }

          // 根據配置生成Invoker物件,該物件是一個基礎服務類,該類抽象了對當前服務的呼叫
          Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass,
              registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
          DelegateProviderMetaDataInvoker wrapperInvoker = new
               DelegateProviderMetaDataInvoker(invoker, this);
          // 將生成的Invoker物件進行暴露
          Exporter<?> exporter = protocol.export(wrapperInvoker);
          exporters.add(exporter);
        }
      } else {
        // 如果沒有註冊中心相關的配置,則會使用預設值來註冊服務,比如註冊中心為zookeeper
        Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);
        DelegateProviderMetaDataInvoker wrapperInvoker = new 
          DelegateProviderMetaDataInvoker(invoker, this);
        Exporter<?> exporter = protocol.export(wrapperInvoker);
        exporters.add(exporter);
      }
      
      // 儲存所暴露的服務的一些元資料資訊
      MetadataReportService metadataReportService = null;
      if ((metadataReportService = getMetadataReportService()) != null) {
        metadataReportService.publishProvider(url);
      }
    }
  }
  this.urls.add(url);
}

        可以看到,這裡的doExportUrlsFor1Protocol()方法就是暴露服務的主要方法。該方法中主要完成了三部分的工作:

  • 構建最終的引數map,其屬性將會用於暴露服務所用;
  • 校驗介面方法配置與需要暴露的方法是否相匹配,如果不匹配,則丟擲異常;
  • 對服務進行暴露,主要包含兩個部分:injvm和registry,其中registry暴露的時候,也會根據相應的協議在本地進行一次暴露,通過這種方式,我們就可以實現本地服務的直連。

4. 小結

        本文主要講解了Dubbo在進行服務暴露工作之前的屬性載入和配置的相關工作,並且我們也對Dubbo所暴露的服務進行了簡要介紹。在後面的文章中,我們將繼續深入介紹Dubbo是如何進