这是我参与2022首次更文挑战的第15天,活动详情查看:2022首次更文挑战
背景
在一些场景下需要在应用允许过程中,动态的修改.properties或者是.yaml中的某个属性值,比如在应用的运行过程中,生产环境出现问题,如何在不重启应用的情况下修改日志的处理级别由INFO修改为DEBUG。当然了,这里假设这里能将值修改成功,由于日志组件已经获取过对应的值,想要对应日志级别修改成功,需要提供回调或者接口的方式进行具体的处理。如果各位的应用群中有引入apollo等配置中心,可以通过配置中心来进行动态的修改属性值,那么配置中心是如何做到修改允许时应用的属性值?
分析
在Springboot的启动过程中,将所有的应用参数等环境变量属性值都解析到类ConfigurableEnvironment中,而对应的.properties和.yaml等配置文件中的属性值就保存在改类的ConfigurablePropertyResolver的PropertySources中,它是一个迭代器,每一个配置资源文件都会解析为一个对象。
如下图所示,这17个PropertySources是我的应用启动后解析的配置源信息。并且红框标注为我自定义的两个配置文件(一个.properties一个.yaml)。
ConfigurableEnvironment#getProperty获取对应的属性值,最终会进入到PropertySourcesPropertyResolver#getProperty中,从这里可以看出,它遍历了这个迭代器,然后通过key获取对应的值,当获取到对应的值后就直接返回。这里做个假设,比如某个属性值source.url这个key在这个迭代器的下标为8和9这两个资源文件中都存在,那么从以上结论中可以得出,这里是获取到了下标为8的资源文件的对应值。
@Nullable
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
if (this.propertySources != null) {
for (PropertySource<?> propertySource : this.propertySources) {
if (logger.isTraceEnabled()) {
logger.trace("Searching for key '" + key + "' in PropertySource '" +
propertySource.getName() + "'");
}
Object value = propertySource.getProperty(key);
if (value != null) {
if (resolveNestedPlaceholders && value instanceof String) {
value = resolveNestedPlaceholders((String) value);
}
logKeyFound(key, propertySource, value);
return convertValueIfNecessary(value, targetValueType);
}
}
}
if (logger.isTraceEnabled()) {
logger.trace("Could not find key '" + key + "' in any property source");
}
return null;
}
从上述分析的结论中,可以得出,如果在项目的允许过程中,想要修改某个key对应的参数值,那么仅需要将修改后的值伪装成PropertySource,添加到这个PropertySources迭代器的头部,那么通过这段代码获取到的值,就是修改后的值。实际上apollo也是通过这种方式实现资源配置项动态更新。
实际上我对这块源码也不熟悉,所以我在测试时伪造了一个和我原本资源文件一样的类对象OriginTrackedMapPropertySource,将它添加到propertySources的头部。
String PROP_SOURCE_NAME = "custom_property";
Map<String, Object> mapProp = new HashMap<>(1);
mapProp.put("source.url","http://xxx");
// 从上述截图可以看出使用的都是OriginTrackedMapPropertySource这个类
OriginTrackedMapPropertySource source = new OriginTrackedMapPropertySource(PROP_SOURCE_NAME, mapProp);
propertySources.addFirst(source);
总结
从以上分析可以得出配置属性值可以在应用运行中动态修改,但是要注意,属性值在动态修改之后,之后再次从这里读取的情况下才会读取到修改之后的值。比如开篇提到的日志的打印级别,单纯的修改这里的配置值你会发现实际上日志的打印级别还是原来一致,这是因为日志组件实际上已经获取过这个配置项的属性值,所以在apollo等配置中心中,部分场景下需要在更新属性配置值之后通过回调接口的方式使得它重新去获取对应的值。