Dubbo配置属性初始化详解

服务器

浏览数:68

2019-7-4

        在前面的文章中,我们讲解了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是如何进行服务暴露的。

作者:爱宝贝丶