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 配置管理器
在回顾调用过程之前,先看下模型、环境以及配置管理器之间的关系,如下图:
在 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 属性变量说明
属性变量名 | 属性类型 | 说明 |
---|---|---|
propertiesConfiguration | PropertiesConfiguration | dubbo.properties 文件中的属性 |
systemConfiguration | SystemConfiguration | JVM 参数启动进程时指定的 (-D)配置 |
environmentConfiguration | EnvironmentConfiguration | 环境变量中的配置 |
externalConfiguration | InmemoryConfiguration | 外部配置全局配置 例如:配置中心 config-center global/default config |
appExternalConfiguration | InmemoryConfiguration | 外部的应用配置 例如:配置中心中执行的当前应用的配置 config-center app config |
appConfiguration | InmemoryConfiguration | 来自应用中的配置 例如:Spring Environment/PropertySources/application.properties |
globalConfiguration | CompositeConfiguration | 前面 6 个配置属性放到一起就是这个 |
globalConfigurationMaps | List<Map<String, String» | 最前面的6个属性转换为 map 放到一起就是这个 可以理解为将全局配置 globalConfiguration 转换成了列表 列表顺序是:SystemConfiguration -> EnvironmentConfiguration -> AppExternalConfiguration -> ExternalConfiguration -> AppConfiguration -> AbstractConfig -> PropertiesConfiguration |
defaultDynamicGlobalConfiguration | CompositeConfiguration | 这个也是一个组合配置将 defaultDynamicConfiguration 动态配置(来自配置中心的配置)和全局配置添加到了自己的配置列表中 列表顺序:defaultDynamicConfiguration -> globalConfiguration |
localMigrationRule | String | 用户在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 方法,完整的配置加载过程在这里整体描述下
项目内配置查询
路径查询
从 JVM 参数重获取配置的 dubbo.properties.file 配置文件路径
若前面未获取到路径则从环境变量参数中,获取配置的 dubbo.properties.file 配置文件路径,如配置:-Ddubbo.properties.file=/dubbo.properties
若前面未获取到路径则使用默认路径的 dubbo.properties
配置加载
将路径转为 FileInputStream,然后使用 Properties 加载
依赖中的配置扫描查询
使用类加载器扫描所有资源 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());
}
}