在一些场景下需要在应用启动或者运行过程中,动态的修改.properties或者是.yaml中的某个属性值。如果有使用过类似携程的Apollo配置中心应该会有所体会。

1. 分析

Springboot的启动过程中,将所有的应用参数等环境变量属性值都解析到类ConfigurableEnvironment中,而对应的.properties.yaml等配置文件中的属性值就保存在改类的ConfigurablePropertyResolverPropertySources中,它是一个迭代器,每一个配置资源文件都会解析为一个对象。 如下图所示,这几个PropertySources是我的应用启动后解析的配置源信息。并且红框标注为我自定义的配置文件(application.yaml)。
image-1658160189987

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也是通过这种方式实现资源配置项动态更新,当然它的实现更加复杂一点,具体请参考文末链接。

想要了解org.springframework.core.env.PropertySource类的话,搜索一下PropertySource类即可。这里使用和读取配置文件一样的类对象OriginTrackedMapPropertySource,将它添加到propertySources的头部。

@Service
public class TestServer implements BeanFactoryPostProcessor, EnvironmentAware {
    private ConfigurableEnvironment environment;

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = (ConfigurableEnvironment) environment;;
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        MutablePropertySources sources = environment.getPropertySources();

        String PROP_SOURCE_NAME = "custom_property";
        Map<String, Object> mapProp = new HashMap<>(1);
        mapProp.put("order.new", "http://xxx");
        OriginTrackedMapPropertySource source = new OriginTrackedMapPropertySource(PROP_SOURCE_NAME, mapProp);
        sources.addFirst(source);
    }
}

动态修改properties配置项值
Apollo配置中心设计