11-Dubbo 配置加载全过程

作者: vnjohn / 发表于 2025-05-31 / 专栏: Dubbo 3.3

Dubbo3, 源码

11.1 回顾启动器初始化过程

在应用程序启动时会调用发布器的启动方法,然后调用初始化方法,回顾发布器初始化方法源码执行过程,如下:

DefaultApplicationDeployer:start -> initialize

public void initialize() {
  if (initialized) {
    return;
  }
  synchronized (startLock) {
    if (initialized) {
      return;
    }
    onInitialize();
    registerShutdownHook();
    startConfigCenter();
    // 本章节主要分析这块部分
    loadApplicationConfigs();
    initModuleDeployers();
    initMetricsReporter();
    initMetricsService();
    initObservationRegistry();
    startMetadataCenter();
    initialized = true;
    if (logger.isInfoEnabled()) {
      logger.info(getIdentifier() + " has been initialized!");
    }
  }
}

初始化过程会先启动配置中心对配置信息进行处理,然后调用加载初始化应用程序配置方法 loadApplicationConfigs 进行配置的加载

Dubbo 框架的配置项比较繁多,为了更好地管理各种配置,将其按照用途划分为不同的组件,最终所有配置项都会汇聚到 URL 中,传递给后续的处理模块

Dubbo 常用的配置组件如下:

  • application:Dubbo 应用配置
  • registry:注册中心
  • protocol:服务提供者 RPC 协议
  • config-center:配置中心
  • metadata-report:元数据中心
  • service:服务提供者配置
  • reference:远程服务引用配置
  • provider:service 默认配置或分组配置
  • consumer:reference 默认配置或分组配置
  • module:模块配置
  • monitor:监控配置
  • metrics:指标配置
  • ssl:SSL/TLS 配置

配置上还有几个重要的点,分别如下:

1、配置来源,从 Dubbo 支持的配置来源说起,默认有 6 种配置来源

  • JVM System Properties:JVM -D 参数
  • System environment:JVM 进程的环境变量
  • Externalized Configuration:外部化配置,从配置中心读取
  • Application Configuration:应用的属性配置,从 Spring 应用的 Environment 中提取"dubbo"打头的属性集
  • API / XML /注解等:编程接口采集的配置可以被理解成配置来源的一种,是直接面向用户编程的配置采集方式
  • 从 classpath 读取配置文件 dubbo.properties

2、覆盖关系,下图展示配置覆盖关系的优先级,从上到下优先级依次降低

dubbo-priority-loading-config.jpg

3、配置方式

  • JAVA API 配置
  • XML 配置
  • Annotation 配置
  • 属性配置

配置虽然非常多,但是我们掌握一下配置加载的原理,再了解下官网的文档说明路径应该基础的配置搞定是没问题的,更深入的配置很多参数还是需要了解下源码的

11.2 配置信息的初始化回顾

在前面说到创建 ModuleModel 对象时,ModuleModel 模型中包含了一个成员变量 ModuleEnvironment 代表当前的模块环境以及成员变量 ModuleConfigManager 配置管理器

ModuleModel 模型对象的父模型对象 ApplicationModel 包含了一个成员变量 Environment 和 ConfigManager 配置管理器

在回顾调用过程之前,先看下模型、环境以及配置管理器之间的关系,如下图:

image-20250518032339325

在 ModuleModel 构造方法中会调用 initModuleExt 方法对模块扩展器集合进行初始化,如下:

private void initModuleExt() {
  // 目前扩展支持类型: ModuleEnvironment、ModuleConfigManager
  Set<ModuleExt> exts = this.getExtensionLoader(ModuleExt.class).getSupportedExtensionInstances();
  for (ModuleExt ext : exts) {
    ext.initialize();
  }
}

ModuleEnvironment 环境信息对象会在创建 ModuleConfigManager 配置管理器时被调用过,如下代码:

ModuleConfigManager 构造方法 —> AbstractConfigManager 构造方法 —> ModuleModel#modelEnvironment

public ModuleEnvironment modelEnvironment() {
  if (moduleEnvironment == null) {
    moduleEnvironment =
      (ModuleEnvironment) this.getExtensionLoader(ModuleExt.class).getExtension(ModuleEnvironment.NAME);
  }
  return moduleEnvironment;
}

通过 ExtensionLoader 扩展加载器将对象 ModuleEnvironment 创建以后,会调用对象的初始化方法 initExtension,其执行的代码如下:

ExtensionLoader:getSupportedExtensionInstances —> getExtension —> createExtension —> initExtension

private void initExtension(T instance) {
  if (instance instanceof Lifecycle) {
    Lifecycle lifecycle = (Lifecycle) instance;
    // ModuleEnvironment
    lifecycle.initialize();
  }
}

11.3 属性加载

11.3.1 Environment 属性初始化方法

该初始化方法对应 ModuleEnvironment 父类型 Environment initialize 初始化方法,如下:

public void initialize() throws IllegalStateException {
  // 乐观锁判断是否进行过初始化
  if (initialized.compareAndSet(false, true)) {
    // PropertiesConfiguration:从系统属性和 dubbo.properties 中获取配置
    this.propertiesConfiguration = new PropertiesConfiguration(scopeModel);
    // SystemConfiguration:获取的是 JVM 参数 启动命令中 -D 指定的
    this.systemConfiguration = new SystemConfiguration();
    // EnvironmentConfiguration:从环境变量中获取的配置
    this.environmentConfiguration = new EnvironmentConfiguration();
    // 外部的 Global 配置 config-center global/default config
    this.externalConfiguration = new InmemoryConfiguration("ExternalConfig");
    // 外部的应用配置如:config-center 中的应用配置
    this.appExternalConfiguration = new InmemoryConfiguration("AppExternalConfig");
    // 本地应用配置,如:Spring Environment/PropertySources/application.properties
    this.appConfiguration = new InmemoryConfiguration("AppConfig");
    // 服务迁移配置加载,dubbo2升级dubbo3的一些配置
    loadMigrationRule();
  }
}

11.3.2 属性变量说明

属性变量名属性类型说明
propertiesConfigurationPropertiesConfigurationdubbo.properties 文件中的属性
systemConfigurationSystemConfigurationJVM 参数启动进程时指定的 (-D)配置
environmentConfigurationEnvironmentConfiguration环境变量中的配置
externalConfigurationInmemoryConfiguration外部配置全局配置
例如:配置中心 config-center global/default config
appExternalConfigurationInmemoryConfiguration外部的应用配置
例如:配置中心中执行的当前应用的配置
config-center app config
appConfigurationInmemoryConfiguration来自应用中的配置
例如:Spring Environment/PropertySources/application.properties
globalConfigurationCompositeConfiguration前面 6 个配置属性放到一起就是这个
globalConfigurationMapsList<Map<String, String»最前面的6个属性转换为 map 放到一起就是这个
可以理解为将全局配置 globalConfiguration 转换成了列表
列表顺序是:SystemConfiguration -> EnvironmentConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AppConfiguration -> AbstractConfig -> PropertiesConfiguration
defaultDynamicGlobalConfigurationCompositeConfiguration这个也是一个组合配置将 defaultDynamicConfiguration 动态配置(来自配置中心的配置)和全局配置添加到了自己的配置列表中
列表顺序:defaultDynamicConfiguration -> globalConfiguration
localMigrationRuleString用户在JVM参数或者环境变量中指定的dubbo.migration.file
如果用户未指定测尝试加载类路径下的 dubbo-migration.yaml

11.3.3 加载 dubbo.properties 文件配置

通过 Environment#initialize 方法调用会先构造 PropertiesConfiguration 类进行配置加载,源码如下:

// PropertiesConfiguration:从系统属性和 dubbo.properties 中获取配置
this.propertiesConfiguration = new PropertiesConfiguration(scopeModel);

直接提取 PropertiesConfiguration 类的构造器源码,如下:

public PropertiesConfiguration(ScopeModel scopeModel) {
  this.scopeModel = scopeModel;
  refresh();
}

public void refresh() {
  // 配置获取的过程是借助工具类 ConfigUtils 来获取的
  properties = ConfigUtils.getProperties(scopeModel.getClassLoaders());
}

继续查看 ConfigUtils#getProperties 方法实现的源码:

public static Properties getProperties(Set<ClassLoader> classLoaders) {
  // 这个配置的 KEY 是 dubbo.properties.file
  // System.getProperty -> System.getProperty:从 JVM 参数中获取配置的,一般情况下我们在启动Java进程的时候会指定 Dubbo 配置文件
  // 如配置:-Ddubbo.properties.file=/dubbo.properties
  String path = SystemPropertyConfigUtils.getSystemProperty(CommonConstants.DubboProperty.DUBBO_PROPERTIES_KEY);
  if (StringUtils.isEmpty(path)) {
    // 优先级最高的 JVM 参数拿不到数据则从环境变量中获取,这个配置 key 也是 dubbo.properties.file
    // System.getenv是从环境变量中获取数据,例如我们在环境变量中配置 dubbo.properties.file=/dubbo.properties
    path = System.getenv(CommonConstants.DubboProperty.DUBBO_PROPERTIES_KEY);
    if (StringUtils.isEmpty(path)) {
      path = CommonConstants.DEFAULT_DUBBO_PROPERTIES;
    }
  }
  // 如果在 JVM 参数和环境变量都拿不到这个配置文件的路径就使用默认的
  // 默认的路径是类路径下的资源文件,这个路径是: dubbo.properties
  return ConfigUtils.loadProperties(classLoaders, path, false, true);
}

路径获取之后加载详细的配置内容,通过调用 ConfigUtils#loadProperties 方法实现,如下:

public static Properties loadProperties(
  Set<ClassLoader> classLoaders, String fileName, boolean allowMultiFile, boolean optional) {
  Properties properties = new Properties();
  // add scene judgement in windows environment Fix 2557
  // 检查文件是否存在,直接加载配置文件,如果加载到了配置文件则直接返回
  if (checkFileNameExist(fileName)) {
    try {
      FileInputStream input = new FileInputStream(fileName);
      try {
        properties.load(input);
      } finally {
        input.close();
      }
    } catch (Throwable e) {
      logger.warn(
        COMMON_IO_EXCEPTION,
        "",
        "",
        "Failed to load " + fileName + " file from " + fileName + "(ignore this file): "
        + e.getMessage(),
        e);
    }
    return properties;
  }
  // 为什么会有下面的逻辑呢,如果仅仅使用上面的加载方式只能加载到本系统下的配置文件,无法加载封装在 jar 中的根路径的配置
  Set<java.net.URL> set = null;
  try {
    List<ClassLoader> classLoadersToLoad = new LinkedList<>();
    classLoadersToLoad.add(ClassUtils.getClassLoader());
    classLoadersToLoad.addAll(classLoaders);
    // 这个方法 loadResources 在扩展加载的时候说过
    set = ClassLoaderResourceLoader.loadResources(fileName, classLoadersToLoad).values().stream()
      .reduce(new LinkedHashSet<>(), (a, i) -> {
        a.addAll(i);
        return a;
      });
  } catch (Throwable t) {
    logger.warn(COMMON_IO_EXCEPTION, "", "", "Fail to load " + fileName + " file: " + t.getMessage(), t);
  }

  if (CollectionUtils.isEmpty(set)) {
    if (!optional) {
      logger.warn(COMMON_IO_EXCEPTION, "", "", "No " + fileName + " found on the class path.");
    }
    return properties;
  }
  if (!allowMultiFile) {
    if (set.size() > 1) {
      String errMsg = String.format(
        "only 1 %s file is expected, but %d dubbo.properties files found on class path: %s",
        fileName, set.size(), set);
      logger.warn(COMMON_IO_EXCEPTION, "", "", errMsg);
    }
    // fall back to use method getResourceAsStream
    try {
      properties.load(ClassUtils.getClassLoader().getResourceAsStream(fileName));
    } catch (Throwable e) {
      logger.warn(
        COMMON_IO_EXCEPTION,
        "",
        "",
        "Failed to load " + fileName + " file from " + fileName + "(ignore this file): "
        + e.getMessage(),
        e);
    }
    return properties;
  }
  logger.info("load " + fileName + " properties file from " + set);
  for (java.net.URL url : set) {
    try {
      Properties p = new Properties();
      InputStream input = url.openStream();
      if (input != null) {
        try {
          p.load(input);
          properties.putAll(p);
        } finally {
          try {
            input.close();
          } catch (Throwable t) {
          }
        }
      }
    } catch (Throwable e) {
      logger.warn(
        COMMON_IO_EXCEPTION,
        "",
        "",
        "Fail to load " + fileName + " file from " + url + "(ignore this file): " + e.getMessage(),
        e);
    }
  }
  return properties;
}

ConfigUtils#getProperties 方法,完整的配置加载过程在这里整体描述下

  1. 项目内配置查询

    路径查询

    从 JVM 参数重获取配置的 dubbo.properties.file 配置文件路径

    若前面未获取到路径则从环境变量参数中,获取配置的 dubbo.properties.file 配置文件路径,如配置:-Ddubbo.properties.file=/dubbo.properties

    若前面未获取到路径则使用默认路径的 dubbo.properties

    配置加载

    将路径转为 FileInputStream,然后使用 Properties 加载

  2. 依赖中的配置扫描查询

    使用类加载器扫描所有资源 URL

    URL 转 InputStream,如 url.openStream(),然后再使用 Properties 加载

11.3.4 加载 JVM 参数配置

继续看 SystemConfiguration 配置加载,该类型仅仅使用 System#getProperty 方法来获取 JVM 参数配置,如下:

public class SystemConfiguration implements Configuration {

    @Override
    public Object getInternalProperty(String key) {
        return System.getProperty(key);
    }

    public Map<String, String> getProperties() {
        return (Map) System.getProperties();
    }
}

11.3.5 加载环境变量参数配置

加载环境变量主要使用的是 EnvironmentConfiguration 类型,该类使用 System#getenv 方法获取环境变量参数,如下:

public class EnvironmentConfiguration implements Configuration {

  @Override
  public Object getInternalProperty(String key) {
    String value = getenv(key);
    if (StringUtils.isEmpty(value)) {
      value = getenv(StringUtils.toOSStyleKey(key));
    }
    return value;
  }

  public Map<String, String> getProperties() {
    return getenv();
  }

  // Adapt to System api, design for unit test
  protected String getenv(String key) {
    return System.getenv(key);
  }

  protected Map<String, String> getenv() {
    return System.getenv();
  }
}

11.3.6 内存配置的封装

全局配置、外部应用配置、本地应用配置都采用 InmemoryConfiguration 类实现加载,其内部使用了一个 LinkedHashMap 来存储相关的配置,源码如下:

public class InmemoryConfiguration implements Configuration {
    private String name;

    // stores the configuration key-value pairs
    private Map<String, String> store = new LinkedHashMap<>();

    public InmemoryConfiguration() {}

    public InmemoryConfiguration(String name) {
        this.name = name;
    }

    public InmemoryConfiguration(Map<String, String> properties) {
        this.setProperties(properties);
    }

    @Override
    public Object getInternalProperty(String key) {
        return store.get(key);
    }

    /**
     * Add one property into the store, the previous value will be replaced if the key exists
     */
    public void addProperty(String key, String value) {
        store.put(key, value);
    }

    /**
     * Add a set of properties into the store
     */
    public void addProperties(Map<String, String> properties) {
        if (properties != null) {
            this.store.putAll(properties);
        }
    }

    /**
     * set store
     */
    public void setProperties(Map<String, String> properties) {
        if (properties != null) {
            this.store = properties;
        }
    }

    public Map<String, String> getProperties() {
        return store;
    }
}

11.3.7 服务升级迁移配置加载

迁移配置文件的名字为 dubbo-migration.yaml,先通过 System#getProperty 方法加载配置文件路径,加载不到再通过 System#getenv 方法进行加载,其实现源码如下:

private void loadMigrationRule() {
  if (Boolean.parseBoolean(SystemPropertyConfigUtils.getSystemProperty(
    CommonConstants.DubboProperty.DUBBO_MIGRATION_FILE_ENABLE, "false"))) {
    // 文件路径配置的key dubbo.migration.file
    // JVM参数中获取
    String path = SystemPropertyConfigUtils.getSystemProperty(CommonConstants.DubboProperty.DUBBO_MIGRATION_KEY);
    if (StringUtils.isEmpty(path)) {
      // env环境变量中获取
      path = System.getenv(CommonConstants.DubboProperty.DUBBO_MIGRATION_KEY);
      if (StringUtils.isEmpty(path)) {
        // 类路径下获取文件dubbo-migration.yaml
        path = CommonConstants.DEFAULT_DUBBO_MIGRATION_FILE;
      }
    }
    this.localMigrationRule = ConfigUtils.loadMigrationRule(scopeModel.getClassLoaders(), path);
  } else {
    this.localMigrationRule = null;
  }
}

11.4 loadApplicationConfigs 方法介绍

通过以上配置内容加载完以后,再来分析初始化方法时加载应用配置是如何实现的,加载配置涉及到了配置优先级的处理,其实现源码:

// org.apache.dubbo.config.deploy.DefaultApplicationDeployer#loadApplicationConfigs
private void loadApplicationConfigs() {
  configManager.loadConfigs();
}

配置管理器加载配置

public void loadConfigs() {
  // application config has load before starting config center
  // load dubbo.applications.xxx
  loadConfigsOfTypeFromProps(ApplicationConfig.class);

  // load dubbo.monitors.xxx
  loadConfigsOfTypeFromProps(MonitorConfig.class);

  // load dubbo.metrics.xxx
  loadConfigsOfTypeFromProps(MetricsConfig.class);

  // load dubbo.tracing.xxx
  loadConfigsOfTypeFromProps(TracingConfig.class);

  // load multiple config types:
  // load dubbo.protocols.xxx
  loadConfigsOfTypeFromProps(ProtocolConfig.class);

  // load dubbo.registries.xxx
  loadConfigsOfTypeFromProps(RegistryConfig.class);

  // load dubbo.metadata-report.xxx
  loadConfigsOfTypeFromProps(MetadataReportConfig.class);

  // config centers has been loaded before starting config center
  // loadConfigsOfTypeFromProps(ConfigCenterConfig.class);
  refreshAll();
  checkConfigs();
  // set model name
  if (StringUtils.isBlank(applicationModel.getModelName())) {
    applicationModel.setModelName(applicationModel.getApplicationName());
  }
}

参考文献

https://cn.dubbo.apache.org/zh-cn/blog/2022/08/14/14-dubbo%E9%85%8D%E7%BD%AE%E5%8A%A0%E8%BD%BD%E5%85%A8%E8%A7%A3%E6%9E%90

vnjohn

作者

vnjohn

后端研发工程师。喜欢探索新技术,空闲时也折腾 AIGC 等效率工具。 可以在 GitHub 关注我了解更多,也可以加我微信(vnjohn) 与我交流。