介绍
在使用spring 时,动态更新配置是常见的,属性值更新,但是需要开启支持刷新功能,一个是spring.cloud.nacos.config.isRefreshEnabled=true; 这个值一般是默认的,可以在nacosConfigProperties这个类中看到。还要在扩展配置中开启refresh = true
spring
cloud:
nacos:
config:
server-addr: ${nacos-ip}
extension-configs[0]:
data-id: ${spring.application.name}.yml
group: base
# 这个地方必须开启,否则不会自动刷新
refresh: true
2 使用
2.1 新建配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "apply.demo")
public class DemoConfig {
/**
* 配置信息
*/
private String config;
}
2.2 新建测试访问类
import com.purgeteam.dynamic.config.starter.event.ActionConfigEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
@RequestMapping("/api/test2")
public class Test2Controller {//
@Autowired
private DemoConfig demoConfig;
@GetMapping(value = "dd")
public String test4(String params){
log.info("dsds" + params);
System.out.println(demoConfig.getConfig());
System.out.println(SpringUtil.getApplicationContext().getBean("testController"));
// 保存数据
return demoConfig.getConfig();
}
}
2.3 yml配置
apply:
demo:
config: 2225dssww
测试结果: 配置中心值改变,对应的属性值也改变
------这里好像没用加@RefreshScope注解
3. 监听到变化触发方法
在监听到配置值变化后,需要触发一些方法,需要实现ApplicationListener<T> 接口,重写onApplicationEvent方法
3.1 触发代码
import com.purgeteam.dynamic.config.starter.event.ActionConfigEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
@Slf4j
@RequestMapping("/api/test2")
public class Test2Controller implements ApplicationListener<ActionConfigEvent> {//
@Autowired
private DemoConfig demoConfig;
@GetMapping(value = "dd")
public String test4(String params){
log.info("dsds" + params);
System.out.println(demoConfig.getConfig());
System.out.println(SpringUtil.getApplicationContext().getBean("testController"));
// 保存数据
return demoConfig.getConfig();
}
// 这个方法会在属性变化后调用,这里可以根据某个值变化去处理其他逻辑
@Override
public void onApplicationEvent(ActionConfigEvent actionConfigEvent) {
// 获取变化的key
Map<String, HashMap> propertyMap = actionConfigEvent.getPropertyMap();
// 取出变化的key的map
HashMap hashMap = propertyMap.get("spring.profiles1.active1");
// 变化后的值
Object after = hashMap.get("after");
// 变化前的值
Object after = hashMap.get("before");
// 打印属性值
log.info("====env========={}",this.demoConfig.getConfig());
}
}
4. 执行流程分析
处理属性变化绑定值类org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
自己可以打断点,看方法走向。
获取服务端配置最后是一个string,在com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable#run这个方法中
String[] ct = ClientWorker.this.getServerConfig(dataId, group, tenant, 3000L);
4.1 服务启动注册配置监听
com.alibaba.cloud.nacos.refresh.NacosContextRefresher#onApplicationEvent这里会调用注册方法com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListenersForApplications
// 注册监听方法
private void registerNacosListenersForApplications() {
// 全局开启刷新,默认是true
if (isRefreshEnabled()) {
for (NacosPropertySource propertySource : NacosPropertySourceRepository
.getAll()) {
// 判断单个配置文件是否支持刷新,就是refresh = true,开启了就会注册监听,有变化就会及时通知
if (!propertySource.isRefreshable()) {
continue;
}
String dataId = propertySource.getDataId();
// 调用注册方法
registerNacosListener(propertySource.getGroup(), dataId);
}
}
}
private void registerNacosListener(final String groupKey, final String dataKey) {
String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
// 创建监听
Listener listener = listenerMap.computeIfAbsent(key,
lst -> new AbstractSharedListener() {
@Override
public void innerReceive(String dataId, String group,
String configInfo) {
refreshCountIncrement();
nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
// todo feature: support single refresh for listening
applicationContext.publishEvent(
new RefreshEvent(this, null, "Refresh Nacos config"));
if (log.isDebugEnabled()) {
log.debug(String.format(
"Refresh Nacos config group=%s,dataId=%s,configInfo=%s",
group, dataId, configInfo));
}
}
});
try {
// 这里是最重要的,将监听添加到,config里,注册上
configService.addListener(dataKey, groupKey, listener);
}
catch (NacosException e) {
log.warn(String.format(
"register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey,
groupKey), e);
}
}
4.2 配置改变刷新配置属性
调用到org.springframework.cloud.context.refresh.ContextRefresher#refreshEnvironment
// 返回变化的key
public synchronized Set<String> refreshEnvironment() {
// 以前的配置的值
Map<String, Object> before = this.extract(this.context.getEnvironment().getPropertySources());
this.addConfigFilesToEnvironment();
// 改变后的key
Set<String> keys = this.changes(before, this.extract(this.context.getEnvironment().getPropertySources())).keySet();
// 发布环境变化时间,spring内部事件
this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
// 返回变化的key
return keys;
}
求出以前和现在变化的key
private Map<String, Object> changes(Map<String, Object> before, Map<String, Object> after) {
Map<String, Object> result = new HashMap();
Iterator var4 = before.keySet().iterator();
String key;
while(var4.hasNext()) {
key = (String)var4.next();
if (!after.containsKey(key)) {
result.put(key, (Object)null);
} else if (!this.equal(before.get(key), after.get(key))) {
result.put(key, after.get(key));
}
}
var4 = after.keySet().iterator();
while(var4.hasNext()) {
key = (String)var4.next();
if (!before.containsKey(key)) {
result.put(key, after.get(key));
}
}
return result;
}
环境变化事件处理
org.springframework.cloud.context.properties.ConfigurationPropertiesRebinder#onApplicationEvent
public void onApplicationEvent(EnvironmentChangeEvent event) {
if (this.applicationContext.equals(event.getSource()) || event.getKeys().equals(event.getSource())) {
// 重新绑定值
this.rebind();
}
}
这里会看到需要绑定的配置类
开始绑定
这里绑定值后,虽然经过了销毁和初始化,发现,地址没有变,里面的值变了
刷新后还会调用这个方法org.springframework.cloud.context.refresh.ContextRefresher#refresh
发布一个RefreshScopeRefreshedEvent事件
com.purgeteam.dynamic.config.starter.event.DynamicConfigApplicationListener#onApplicationEvent
public void onApplicationEvent(RefreshEvent event) {
ConfigurableEnvironment beforeEnv = (ConfigurableEnvironment)this.context.getEnvironment();
MutablePropertySources propertySources = beforeEnv.getPropertySources();
MutablePropertySources beforeSources = new MutablePropertySources(propertySources);
Set<String> refresh = this.refresh.refresh();
Map<String, HashMap> contrast = this.propertyUtil.contrast(beforeSources, propertySources);
// 发布一个配置变化事件
this.context.publishEvent(new ActionConfigEvent(this, "Refresh config", contrast));
log.info("[ActionApplicationListener] The update is successful {}", refresh);
}
调用到自己的方法
public void onApplicationEvent(ActionConfigEvent actionConfigEvent) {
Map<String, HashMap> propertyMap = actionConfigEvent.getPropertyMap();
HashMap hashMap = propertyMap.get("spring.profiles1.active1");
Object after = hashMap.get("after");
// 触发其他逻辑
log.info("====env========={}",this.demoConfig.getConfig());
}
5. 总结
这里主要介绍了使用。
如果属性刷新需要处理逻辑,就需要实现ApplicationListener接口
需要注意配置是怎么注册监听的。