介绍
在使用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
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 {// @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 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
// 返回变化的keypublic synchronized Set refreshEnvironment() {// 以前的配置的值 Map before = this.extract(this.context.getEnvironment().getPropertySources()); this.addConfigFilesToEnvironment();// 改变后的key Set 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 changes(Map before, Map after) { Map 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 refresh = this.refresh.refresh(); Map 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 propertyMap = actionConfigEvent.getPropertyMap(); HashMap hashMap = propertyMap.get("spring.profiles1.active1"); Object after = hashMap.get("after");// 触发其他逻辑 log.info("====env========={}",this.demoConfig.getConfig()); }
5. 总结
这里主要介绍了使用。
如果属性刷新需要处理逻辑,就需要实现ApplicationListener接口
需要注意配置是怎么注册监听的。
来源地址:https://blog.csdn.net/wangfenglei123456/article/details/128634050