背景
在Spring项目中,已有一个被@RefreshScope注解标注的@Configuration配置类,希望手写一段代码,使调用这段代码的方法可以直接刷新某个配置项(多个同理)
原理
学习Nacos的热刷新,完成类似的功能。 通过更新SpringContext的Environment中的properties来实现动态更改配置项的值,然后调用Refresher来刷新SpringContext已注入的配置。 具体操作和原理如下
先贴代码
//公共配置文件
@RefreshScope
@Getter
@Configuration
public class UserConfig {
@Value("${my.config.user.namespace}")
private String userNameSpace;
}
//一个刷新上面UserConfig公共配置文件中userNameSpace的值的方法
private void refreshUserConfig() {
//获取待修改配置项要修改成为的值,根据自己的业务和场景自己通过自己的方法获取
String namespace = userService.getUserNameSpace();
//获取Spring上下文,这里简单写了个SpringUtil类快捷获取,具体SpringUtil内容可上网查阅
ApplicationContext context = SpringContextUtil.getApplicationContext();
//获取ConfigurationEnvironment,就是存放所有读取的配置文件和配置文件内容的地方
ConfigurableEnvironment environment = (ConfigurableEnvironment) context.getEnvironment();
//获取environment中的配置来源列表(包括各类系统配置、配置文件配置,配置中心配置等)
MutablePropertySources propertySources = environment.getPropertySources();
//增加一个配置项。注意,这是原来配置文件中没有该配置项的情况。如果你是要获取某个配置去修改它的值,需要先知道该配置是从哪里拿到的(哪个配置文件、配置中心之类的),从PropertySources找到对应name的properties项,将其中的这个配置项修改掉。
propertySources.addFirst(
new MapPropertySource("newSource",
Collections.singletonMap("my.config.user.namespace",namespace)));
//获取ContextRefresh的bean,nacos也是使用该refresher进行@RefreshScope标注的配置列表刷新
Map<String, ContextRefresher> beansOfType = SpringContextUtil.getBeansOfType(ContextRefresher.class);
ContextRefresher refresher = beansOfType.get("legacyContextRefresher");
//刷新配置
refresher.refresh();
//检查是否刷新成功
log.info("config:"+ruleConfig.getUserNameSpace());
}
实现原理
- 在配置类上增加@RefreshScope注解。被@RefreshScope注解的类会使用一个@Scope注解,并默认添加一个name属性为“refresh”。该属性会标识一个范围,用于后续的配置刷新使用。
- 首先了解,我们的配置加载,是在springcontext中的ConfigurableEnvironment中,在enviroment中的propertySource列表中,我们可以查找我们所有加载的配置,包括从配置文件、配置中心或三方依赖中加载到的配置。
- 配置刷新的方法 我们通过主动地更改enviroment中的propertySource列表中的配置项的值,或向其中进行配置增加,然后通过contextRefresher的refresh动作,对contextRefresher对应的scope进行配置重新赋值。当前默认的name为“refresh”的scope对应的refresher为legacyContextRefresher。
其父抽象类ContextRefresher中可以找到关联的RefreshScope
refresh方法中即调用scope的refresh。
推荐使用场景
- 自定义配置的刷新。当我们有在项目中自己定义配置或者自己定义配置中心时,需要自己定义刷新范围和刷新逻辑,即可使用手动的refresh。
- 在项目启动时,动态地从外部获取一些配置项,加载到配置中全局调用,即可在启动时使用一个启动监听器,进行初始化的refresh。