每日一句:
真正的价值并不在人生的舞台上,而在我们扮演的角色中。
前言
前面都是在说Archaius是如何管理配置 配置如何事项动态性的。但是作为一个配置组件 多环境配置是必不可少的。
示例一
在classpath下面定义了如下的4个配置文件。
========application.properties========
person.name=coredy
person.age=29
=======application-dev.properties=====
person.name=dev_coredy
person.age=dev_29
=======application-test.properties=====
person.name=test_coredy
person.age=test_29
=======application-pro.properties=====
person.name=pro_coredy
person.age=pro_29
测试代码
System.setProperty(DeploymentContext.ContextKey.environment.getKey() , "pro");
ConfigurationManager.loadCascadedPropertiesFromResources("application");
String name = ConfigurationManager.getConfigInstance().getString("person.name");
assertThat(name).isEqualTo("pro_coredy");
上面的demo很简单 在程序初始化的时候设置DeploymentContext.ContextKey.environment.getKey()为生产环境。并且调用loadCascadedPropertiesFromResources来加载application.properties文件。这个方法非常关键!从名字可以看出是级联加载的意思。 这样我们就能轻松的实现 多环境的部署。
但是如果在classpath下面出现了config.properties配置文件 并且里面有相同的属性 就会覆盖application-x.properties配置。 具体原因我们后续分析。
示例二
在classpath下面定义了如下的4个配置文件。
========application.properties========
person.name=coredy
person.age=29
@next=database-${@region}.properties
=======database-dev.properties=====
url=dev-databaseurl
=======database-test.properties=====
url=test-databaseurl
=======database-pro.properties=====
url=pro-databaseurl
测试代码
System.setProperty(DeploymentContext.ContextKey.region.getKey() , "dev");
ConfigurationManager.loadCascadedPropertiesFromResources("application");
String url = ConfigurationManager.getConfigInstance().getString("url");
assertThat(url).isEqualTo("dev-databaseurl");
同样也能实现 多环境配置的目的。这里使用了一个@next特定属性来级联加载配置文件。或许就是loadCascadedPropertiesFromResources这个方法名字的含义了。
ConfigurationManager.loadCascadedPropertiesFromResources
可以看到上面的两个例子 都是和loadCascadedPropertiesFromResources这个方法有关。那么一定是这个方法在其中发挥着作用。下面我们就深入这个方法的代码分析一下为什么会有上面的结果
跟进:loadCascadedPropertiesFromResources
//这里不用说 是加载级联配置文件
Properties props = loadCascadedProperties(configName);
//下面这里也需要注意一下,instance是一个组合配置 加载回来的配置
//会被instance.addConfiguration 意味着什么?意味着我们上面加载回来的配置
//是放在instance最后面的 instance是一个组合配置 内部维护了一个Configration列表
//那也就是说 优先级是低于 默认配置和系统配置等等。默认配置就是config.peoperties
if (instance instanceof AggregatedConfiguration) {
ConcurrentMapConfiguration config = new ConcurrentMapConfiguration();
config.loadProperties(props);
((AggregatedConfiguration) instance).addConfiguration(config, configName);
} else {
ConfigurationUtils.loadProperties(props, instance);
}
上面的loadCascadedProperties这个方法是关键 继续进入。
private static Properties loadCascadedProperties(String configName) throws IOException {
//拼接配置文件名字 所以我们在传参的时候注意不能带有.properties后缀
String defaultConfigFileName = configName + ".properties";
//下面如果Configuration
if (instance == null) {
instance = getConfigInstance();
}
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(defaultConfigFileName);
if (url == null) {
throw new IOException("Cannot locate " + defaultConfigFileName + " as a classpath resource.");
}
//加载配置文件注意名字是defaultConfigFileName
Properties props = getPropertiesFromFile(url);
//获取部署信息的上下午 这个值是我们设置进去的
String environment = getDeploymentContext().getDeploymentEnvironment();
if (environment != null && environment.length() > 0) {
//如果环境变量不为null 拼接名字 加载属性
String envConfigFileName = configName + "-" + environment + ".properties";
url = loader.getResource(envConfigFileName);
if (url != null) {
//最终也是调用getPropertiesFromFile来解析配置
Properties envProps = getPropertiesFromFile(url);
if (envProps != null) {
props.putAll(envProps);
}
}
}
return props;
}
到这里 我们示例一中的多环境配置已经一目了然了。就是上面这个方法做了一个environment的取值然后如果不为空 就重新拼接了名字 继续加载配置。那么示例二中的级联配置还是没有体现。下面我们继续进入getPropertiesFromFile 这个方法。
public static Properties getPropertiesFromFile(URL startingUrl)
throws FileNotFoundException {
return ConfigurationUtils.getPropertiesFromFile(startingUrl, getLoadedPropertiesURLs(), "@next", "netflixconfiguration.properties.nextLoad");
}
看到这个方法 看到传的参数 其实真相已经大白。下面我们进入ConfigurationUtils.getPropertiesFromFile看一下是怎么处理的。
//这个方法没什么看的 主要的逻辑是交给了getConfigFromPropertiesFile来处理的
//loaded 这个参数是用来保存已经加载了的配置文件的地址
public static Properties getPropertiesFromFile(URL startingUrl, Set<String> loaded, String... nextLoadKeys)
throws FileNotFoundException {
AbstractConfiguration config = getConfigFromPropertiesFile(startingUrl, loaded, nextLoadKeys);
return getProperties(config);
}
public static AbstractConfiguration getConfigFromPropertiesFile(URL startingUrl, Set<String> loaded, String... nextLoadKeys)
throws FileNotFoundException {
if (loaded.contains(startingUrl.toExternalForm())) {
logger.warn(startingUrl + " is already loaded");
return null;
}
PropertiesConfiguration propConfig = null;
try {
//OverridingPropertiesConfiguration 这个类是ConfigurationUtils的内部类你可以它看成PropertiesConfigration
//唯一的区别是 addProperty的时候 如果有相同key的属性 它是先清空 再添加。
propConfig = new OverridingPropertiesConfiguration(startingUrl);
logger.info("Loaded properties file " + startingUrl);
} catch (ConfigurationException e) {
Throwable cause = e.getCause();
if (cause instanceof FileNotFoundException) {
throw (FileNotFoundException) cause;
} else {
throw new RuntimeException(e);
}
}
//如果 @next 没有配置 直接返回主配置文件了
if (nextLoadKeys == null) {
return propConfig;
}
//下面两行代码找到主配置文件的 文件路径
String urlString = startingUrl.toExternalForm();
String base = urlString.substring(0, urlString.lastIndexOf("/"));
//将已经加载的主配置文件加入loaded集合。
loaded.add(startingUrl.toString());
//加载@next属性 对应的配置文件
loadFromPropertiesFile(propConfig, base, loaded, nextLoadKeys);
return propConfig;
}
继续跟进loadFromPropertiesFile
static void loadFromPropertiesFile(AbstractConfiguration config, String baseUrl, Set<String> loaded, String... nextLoadKeys) {
// 找@next对应的值 config:是主配置文件解析出来的 baseUrl是主配置文件目录
String nextLoad = getNextLoad(config, nextLoadKeys);
if (nextLoad == null) {
return;
}
//解析出来是多个值
String[] filesToLoad = nextLoad.split(",");
//逐个解析
for (String fileName: filesToLoad) {
fileName = fileName.trim();
try {
URL url = new URL(baseUrl + "/" + fileName);
// avoid circle
if (loaded.contains(url.toExternalForm())) {
logger.warn(url + " is already loaded");
continue;
}
//还是一样将解析过的路径加入loaded
loaded.add(url.toExternalForm());
//注意这里 实例化的是一个OverridingPropertiesConfiguration
//config是主配置文件解析的内容 copyProperties这个方法会覆盖主配置文件的内容 从而子配置文件生效
//这里其实实例一个PropertiesConfiguration实例也能达到效果。前面说了
//OverridingPropertiesConfiguration和PropertiesConfiguration唯一的区别就是addProperty方法 前者调用了clear直接清除 属于完全覆盖
//但是copyProperties方法并没有调用addProperty方法 而是调用setProperty
PropertiesConfiguration nextConfig = new OverridingPropertiesConfiguration(url);
copyProperties(nextConfig, config);
logger.info("Loaded properties file " + url);
//这里是一个递归调用。因为有可能加载回来的子配置文件里面还有@next配置 所以需要getNextLoad再一次解析
loadFromPropertiesFile(config, baseUrl, loaded, nextLoadKeys);
} catch (Throwable e) {
logger.warn("Unable to load properties file", e);
}
}
}
结束语
流程到这就结束了,通过翻阅源码 解答了我们上面的两个示例工作的原理。其实读到这里 如果翻阅Spring Environment源码的朋友可能会发现 有点相似(如果对SpringEnvironment感兴趣的这里有SpringBoot源码系列:Environment机制深入分析(一) )。或许Spring的Environment就是借鉴Archaius的 ,又或许它借鉴了Spring。不管谁借鉴了谁,这何尝不是我们学习源码的意义。