Nacos:配置中心源码简单分析

Nacos:配置中心

本次使用nacos的版本是1.4.1
在spring cloud中,要想使用nacos的配置管理功能,需要引入如下:

        <!--nacos-config-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

作为一个starter,首先关注的就是它的META_INF/spring.factories文件的内容,看看引入该starter会自动装配哪些bean

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,\
com.alibaba.cloud.nacos.endpoint.NacosConfigEndpointAutoConfiguration
org.springframework.boot.diagnostics.FailureAnalyzer=\
com.alibaba.cloud.nacos.diagnostics.analyzer.NacosConnectionFailureAnalyzer
org.springframework.boot.env.PropertySourceLoader=\
com.alibaba.cloud.nacos.parser.NacosJsonPropertySourceLoader,\
com.alibaba.cloud.nacos.parser.NacosXmlPropertySourceLoader

nacos作为配置中心,不仅可以适应多环境配置信息的灵活切换,同时也支持配置的动态刷新

初始化配置信息

根据starter中META_INF/spring.factories文件的内容,可以知道,springBoot启动的时候,会自动加载com.alibaba.cloud.nacos.NacosConfigBootstrapConfiguration 这个类,从nacos服务端获取配置信息,该类注入了三个bean,NacosConfigProperties、NacosConfigManager、NacosPropertySourceLocator ,其中NacosConfigProperties 是收集我们的配置信息的,NacosConfigManager 的主要作用是创建了NacosConfigService 的实例,重点关注是NacosPropertySourceLocator,它实现了org.springframework.cloud.bootstrap.config.PropertySourceLocator 接口,重写了locate方法实现了配置信息的初始化,NacosPropertySourceLocator#locate 方法是实现如下:

    public PropertySource<?> locate(Environment env) {
        this.nacosConfigProperties.setEnvironment(env);
        // 从配置管理器中获取配置服务
        ConfigService configService = this.nacosConfigManager.getConfigService();
        if (null == configService) {
            log.warn("no instance of config service found, can't load config from nacos");
            return null;
        } else {
            long timeout = (long)this.nacosConfigProperties.getTimeout();
            this.nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout);
            String name = this.nacosConfigProperties.getName();
            String dataIdPrefix = this.nacosConfigProperties.getPrefix();
            if (StringUtils.isEmpty(dataIdPrefix)) {
                dataIdPrefix = name;
            }
            // 没有配置前缀,默认使用spring.application.name的值
            if (StringUtils.isEmpty(dataIdPrefix)) {
                dataIdPrefix = env.getProperty("spring.application.name");
            }

            CompositePropertySource composite = new CompositePropertySource("NACOS");
            // 先加载共享配置
            this.loadSharedConfiguration(composite);
            // 再加载拓展配置
            this.loadExtConfiguration(composite);
            // 最后加载应用配置
            this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env);
            return composite;
        }
    }

从这里也可以看出配置的优先级,后面加载的会覆盖前面的配置,其配置的优先级是:应用配置 > 拓展配置 > 共享配置 三种配置加载,最终都是通过

NacosPropertySourceLocator#loadNacosDataIfPresent 来实现的:

    private void loadNacosDataIfPresent(CompositePropertySource composite, String dataId, String group, String fileExtension, boolean isRefreshable) {
        if (null != dataId && dataId.trim().length() >= 1) {
            if (null != group && group.trim().length() >= 1) {
                // 核心代码,加载nacos服务端的配置信息
                NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group, fileExtension, isRefreshable);
                this.addFirstPropertySource(composite, propertySource, false);
            }
        }
    }

接着看NacosPropertySourceLocator#loadNacosPropertySource

    private NacosPropertySource loadNacosPropertySource(String dataId, String group, String fileExtension, boolean isRefreshable) {
        return NacosContextRefresher.getRefreshCount() != 0L && !isRefreshable ? NacosPropertySourceRepository.getNacosPropertySource(dataId, group) : this.nacosPropertySourceBuilder.build(dataId, group, fileExtension, isRefreshable);
    }

如果不用刷新,就使用本地文件系统存储的快照配置信息,否则就走动态刷新的途径获取配置信息。我们以动态刷新为例,往下走,来到NacosPropertySourceBuilder#build

    NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) {
        // 从nacos服务端加载配置信息
        List<PropertySource<?>> propertySources = this.loadNacosData(dataId, group, fileExtension);
        // 封装配置信息
        NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources, group, dataId, new Date(), isRefreshable);
        // 将配置信息收集到NacosPropertySourceRepository 
        NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
        return nacosPropertySource;
    }

重点看加载配置信息的过程 NacosPropertySourceBuilder#loadNacosData

    private List<PropertySource<?>> loadNacosData(String dataId, String group, String fileExtension) {
        String data = null;

        try {
            // 通过configService加载配置信息
            data = this.configService.getConfig(dataId, group, this.timeout);
            if (StringUtils.isEmpty(data)) {
                log.warn("Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]", dataId, group);
                return Collections.emptyList();
            }

            if (log.isDebugEnabled()) {
                log.debug(String.format("Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId, group, data));
            }
            // 解析配置信息,并封装成List<PropertySource<?>>
            return NacosDataParserHandler.getInstance().parseNacosData(dataId, data, fileExtension);
        } catch (NacosException var6) {
            log.error("get data from Nacos error,dataId:{} ", dataId, var6);
        } catch (Exception var7) {
            log.error("parse data from Nacos error,dataId:{},data:{}", new Object[]{dataId, data, var7});
        }

        return Collections.emptyList();
    }

这个方法的逻辑比较清晰,首先加载配置信息,然后解析配置信息,我们关注加载配置信息的部分,来到NacosConfigService#getConfig

    public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
        return this.getConfigInner(this.namespace, dataId, group, timeoutMs);
    }

getConfigInner 方法里面会调用ClientWorker#getServerConfig

String[] ct = this.worker.getServerConfig(dataId, group, tenant, timeoutMs);

跟进去,看一下ClientWorker#getServerConfig

    public String[] getServerConfig(String dataId, String group, String tenant, long readTimeout) throws NacosException {
        String[] ct = new String[2];
        if (StringUtils.isBlank(group)) {
            group = "DEFAULT_GROUP";
        }

        HttpRestResult result = null;

        try {
            Map<String, String> params = new HashMap(3);
            if (StringUtils.isBlank(tenant)) {
                params.put("dataId", dataId);
                params.put("group", group);
            } else {
                params.put("dataId", dataId);
                params.put("group", group);
                params.put("tenant", tenant);
            }
            // 通过http请求拉取配置信息
            result = this.agent.httpGet("/v1/cs/configs", (Map)null, params, this.agent.getEncode(), readTimeout);
        } catch (Exception var10) {
            String message = String.format("[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s", this.agent.getName(), dataId, group, tenant);
            LOGGER.error(message, var10);
            throw new NacosException(500, var10);
        }
        // 处理结果
        switch(result.getCode()) {
        // 正常返回
        case 200: 
            // 保存快照到本地文件系统
            LocalConfigInfoProcessor.saveSnapshot(this.agent.getName(), dataId, group, tenant, (String)result.getData());
            ct[0] = (String)result.getData(); // 配置信息的内容
            if (result.getHeader().getValue("Config-Type") != null) {
                ct[1] = result.getHeader().getValue("Config-Type");
            } else {
                ct[1] = ConfigType.TEXT.getType();
            }

            return ct;
        case 403:
            LOGGER.error("[{}] [sub-server-error] no right, dataId={}, group={}, tenant={}", new Object[]{this.agent.getName(), dataId, group, tenant});
            throw new NacosException(result.getCode(), result.getMessage());
        case 404:
            LocalConfigInfoProcessor.saveSnapshot(this.agent.getName(), dataId, group, tenant, (String)null);
            return ct;
        case 409:
            LOGGER.error("[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, tenant={}", new Object[]{this.agent.getName(), dataId, group, tenant});
            throw new NacosException(409, "data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
        default:
            LOGGER.error("[{}] [sub-server-error]  dataId={}, group={}, tenant={}, code={}", new Object[]{this.agent.getName(), dataId, group, tenant, result.getCode()});
            throw new NacosException(result.getCode(), "http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
        }
    }

这里就是通过http请求/v1/cs/configs来拉取配置信息,同时配置信息保存到本地文件。到这里就很明了了,nacos客户端配置信息的获取,本质是通过http请求和nacos服务端进行通信。

动态刷新配置信息

根据starter中META_INF/spring.factories文件的内容,可以知道,springBoot启动的时候,会自动加载com.alibaba.cloud.nacos.NacosConfigAutoConfiguration,nacos的动态刷新配置入口就在这里,该类会自动注入NacosContextRefresher,而NacosContextRefresher 实现了 ApplicationListener<ApplicationReadyEvent>接口,所以在监听到ApplicationReadyEvent事件时,会触发onApplicationEvent 方法,NacosContextRefresher#onApplicationEvent 方法如下

    public void onApplicationEvent(ApplicationReadyEvent event) {
        if (this.ready.compareAndSet(false, true)) {
            this.registerNacosListenersForApplications();
        }
    }

registerNacosListenersForApplications 方法会调用该类的registerNacosListener

    private void registerNacosListener(String groupKey, String dataKey) {
        // 构建key
        String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
        Listener listener = (Listener)this.listenerMap.computeIfAbsent(key, (lst) -> {
            return new AbstractSharedListener() {
                public void innerReceive(String dataId, String group, String configInfo) {
                    NacosContextRefresher.refreshCountIncrement();
                    NacosContextRefresher.this.nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
                    NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));
                    if (NacosContextRefresher.log.isDebugEnabled()) {
                        NacosContextRefresher.log.debug(String.format("Refresh Nacos config group=%s,dataId=%s,configInfo=%s", group, dataId, configInfo));
                    }

                }
            };
        });

        try {
            // 添加监听器
            this.configService.addListener(dataKey, groupKey, listener);
        } catch (NacosException var6) {
            log.warn(String.format("register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey, groupKey), var6);
        }

    }

NacosPropertySourceRepository就是初始化配置信息后存放的位置,现在重点关注如下代码

            // 添加监听器
            this.configService.addListener(dataKey, groupKey, listener);

NacosConfigService#addListener会调用ClientWorker#addTenantListeners

    public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners) throws NacosException {
        group = this.null2defaultGroup(group);
        String tenant = this.agent.getTenant();
        CacheData cache = this.addCacheDataIfAbsent(dataId, group, tenant);
        Iterator var6 = listeners.iterator();

        while(var6.hasNext()) {
            Listener listener = (Listener)var6.next();
            // 添加监听器
            cache.addListener(listener);
        }

    }

这里又调用了CacheData#addListener,只是封装成CacheData.ManagerListenerWrap,然后存储在private final CopyOnWriteArrayList<CacheData.ManagerListenerWrap> listeners该属性中。

我们把焦点转移到ClientWorker:

    public ClientWorker(final HttpAgent agent, ConfigFilterChainManager configFilterChainManager, Properties properties) {
        this.agent = agent;
        this.configFilterChainManager = configFilterChainManager;
        this.init(properties);
        this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });
        this.executorService = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), new ThreadFactory() {
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r);
                t.setName("com.alibaba.nacos.client.Worker.longPolling." + agent.getName());
                t.setDaemon(true);
                return t;
            }
        });
        this.executor.scheduleWithFixedDelay(new Runnable() {
            public void run() {
                try {
                    ClientWorker.this.checkConfigInfo();
                } catch (Throwable var2) {
                    ClientWorker.LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", var2);
                }

            }
        }, 1L, 10L, TimeUnit.MILLISECONDS);
    }

在其构造器中,定义了两个线程池,其中线程池executor每隔10毫秒执行一次调度任务,线程池executorService执行检查配置更新任务,所以关注更新配置代码

 ClientWorker.this.checkConfigInfo();
    public void checkConfigInfo() {
        // 监听器的数量
        int listenerSize = this.cacheMap.size();
        // 长轮询任务的数量,计算方法是:将需要刷新的数据分组,每3000个为一组,一组由一个线程处理,每组一个taskId
        int longingTaskCount = (int)Math.ceil((double)listenerSize / ParamUtil.getPerTaskConfigSize());
        if ((double)longingTaskCount > this.currentLongingTaskCount) {
            for(int i = (int)this.currentLongingTaskCount; i < longingTaskCount; ++i) {
                this.executorService.execute(new ClientWorker.LongPollingRunnable(i));
            }

            this.currentLongingTaskCount = (double)longingTaskCount;
        }

    }

这段代码刚看的时候有些费解,仔细想一下就会发现,只是进行了分组而已,应该是为了避免启动过多的线程

同时可以看到,这里执行的任务是ClientWorker的内部类LongPollingRunnable,LongPollingRunnable实现了Runnable接口,所以主要关注run方法

        // LongPollingRunnable中run方法代码比较多,只拿了部分代码
        // 检查配置信息发生变化的groupkeys,其实就是dataId和group
        List<String> changedGroupKeys = ClientWorker.this.checkUpdateDataIds(cacheDatas, inInitializingCacheList);
                if (!CollectionUtils.isEmpty(changedGroupKeys)) {
                    ClientWorker.LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
                }
                // 下面就是根据groupkeys到服务端拉取发生变更的配置信息
                Iterator var16 = changedGroupKeys.iterator();

                while(var16.hasNext()) {
                    String groupKey = (String)var16.next();
                    String[] key = GroupKey.parseKey(groupKey);
                    String dataId = key[0];
                    String group = key[1];
                    String tenant = null;
                    if (key.length == 3) {
                        tenant = key[2];
                    }

                    try {
                       // 根据dataId和group到服务端拉取发生变更的配置信息
                        String[] ct = ClientWorker.this.getServerConfig(dataId, group, tenant, 3000L);
                        CacheData cache = (CacheData)ClientWorker.this.cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
                        // 更新配置信息
                        cache.setContent(ct[0]);
                        if (null != ct[1]) {
                            cache.setType(ct[1]);
                        }

                        ClientWorker.LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}", new Object[]{ClientWorker.this.agent.getName(), dataId, group, tenant, cache.getMd5(), ContentUtils.truncateContent(ct[0]), ct[1]});
                    } catch (NacosException var12) {
                        String message = String.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s", ClientWorker.this.agent.getName(), dataId, group, tenant);
                        ClientWorker.LOGGER.error(message, var12);
                    }
                }

这里主要分为两大步骤:

  1. 检查配置信息发生变化的groupkeys,其实就是dataId和group
  2. 根据dataId和group到服务端拉取发生变更的配置信息

拉取配置信息,和初始化配置信息是一样的,通过http调用服务端开放的api,所以重点在于它是知道配置信息发生了变更的?也就是第一步是如何进行的。

首先是看到是调用了checkUpdateDataIds方法,该方法又调用了checkUpdateConfigStr方法

    List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws Exception {
        Map<String, String> params = new HashMap(2);
        params.put("Listening-Configs", probeUpdateString);
        Map<String, String> headers = new HashMap(2);
        // 在请求头添加长轮询的超时时间
        headers.put("Long-Pulling-Timeout", "" + this.timeout);
        if (isInitializingCacheList) {
            headers.put("Long-Pulling-Timeout-No-Hangup", "true");
        }

        if (StringUtils.isBlank(probeUpdateString)) {
            return Collections.emptyList();
        } else {
            try {
                // 设置http请求的超时时间,大约是长轮询超时时间的1.5倍
                long readTimeoutMs = this.timeout + (long)Math.round((float)(this.timeout >> 1));
                HttpRestResult<String> result = this.agent.httpPost("/v1/cs/configs/listener", headers, params, this.agent.getEncode(), readTimeoutMs);
                if (result.ok()) {
                    this.setHealthServer(true);
                    return this.parseUpdateDataIdResponse((String)result.getData());
                }

                this.setHealthServer(false);
                LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", this.agent.getName(), result.getCode());
            } catch (Exception var8) {
                this.setHealthServer(false);
                LOGGER.error("[" + this.agent.getName() + "] [check-update] get changed dataId exception", var8);
                throw var8;
            }

            return Collections.emptyList();
        }
    }

可以看到,nacos客户端感受服务端配置信息是否发生变更,是通过http长轮询实现的,长轮询的超时默认是ClientWorker类的timeout属性,timeout在init方法设置了值,而ClientWorker的构造器中会执行init方法,从而设置检查配置的时间,最少是10秒,默认是30秒

    private void init(Properties properties) {
        this.timeout = (long)Math.max(ConvertUtils.toInt(properties.getProperty("configLongPollTimeout"), 30000), 10000);
        this.taskPenaltyTime = ConvertUtils.toInt(properties.getProperty("configRetryTime"), 2000);
        this.enableRemoteSyncConfig = Boolean.parseBoolean(properties.getProperty("enableRemoteSyncConfig"));
    }

在根据dataId和group到服务端拉取发生变更的配置信息后,是如何将配置信息更新到我们应用中的呢?注意到在将配置信息更新到了CacheData对象的content属性之后,调用了CacheData对象的checkListenerMd5方法

    void checkListenerMd5() {
        Iterator var1 = this.listeners.iterator();
        // 遍历所有的监听器
        while(var1.hasNext()) {
            CacheData.ManagerListenerWrap wrap = (CacheData.ManagerListenerWrap)var1.next();
            // 对比配置信息的md5,如果不一致,就是配置信息发生了变化,通知监听器处理
            if (!this.md5.equals(wrap.lastCallMd5)) {
                this.safeNotifyListener(this.dataId, this.group, this.content, this.type, this.md5, wrap);
            }
        }

    }

可以看到,当配置信息发生了变化,就会通知监听器进行处理,CacheData#safeNotifyListener方法的部分代码

                    Thread.currentThread().setContextClassLoader(appClassLoader);
                    ConfigResponse cr = new ConfigResponse();
                    cr.setDataId(dataId);
                    cr.setGroup(group);
                    cr.setContent(content);
                    CacheData.this.configFilterChainManager.doFilter((IConfigRequest)null, cr);
                    String contentTmp = cr.getContent();
                    listener.receiveConfigInfo(contentTmp);

可以看到,这里会触发监听器的回调方法receiveConfigInfo,这样一来,监听器的持有者就可以获取到最新的配置信息了

配置信息的热部署

经过前面的分析,可以知道,当配置信息发生了变化,就会触发监听器的回调方法receiveConfigInfo

listener.receiveConfigInfo(contentTmp);

那么这个回调方法是如何处理的呢?它怎么就实现了配置信息的热部署呢?如果你跟踪源码的细心一点,就会留意到NacosContextRefresher的registerNacosListener方法,就是添加监听器的源头,其传递路径是这样的:

  1. NacosContextRefresher#registerNacosListener
  2. NacosConfigService#addListener
  3. ClientWorker#addTenantListeners
  4. CacheData#addListener

下面看一下NacosContextRefresher#registerNacosListener

    private void registerNacosListener(String groupKey, String dataKey) {
        String key = NacosPropertySourceRepository.getMapKey(dataKey, groupKey);
        Listener listener = (Listener)this.listenerMap.computeIfAbsent(key, (lst) -> {
            return new AbstractSharedListener() {
                public void innerReceive(String dataId, String group, String configInfo) {
                    NacosContextRefresher.refreshCountIncrement();
                    NacosContextRefresher.this.nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
                    NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));
                    if (NacosContextRefresher.log.isDebugEnabled()) {
                        NacosContextRefresher.log.debug(String.format("Refresh Nacos config group=%s,dataId=%s,configInfo=%s", group, dataId, configInfo));
                    }

                }
            };
        });

        try {
            this.configService.addListener(dataKey, groupKey, listener);
        } catch (NacosException var6) {
            log.warn(String.format("register fail for nacos listener ,dataId=[%s],group=[%s]", dataKey, groupKey), var6);
        }

    }

这里调用的是innerReceive,并不是receiveConfigInfo,因为receiveConfigInfo方法调用了innerReceive方法,所以最终会调用innerReceive方法

public abstract class AbstractSharedListener implements Listener {
    private volatile String dataId;
    private volatile String group;

    public AbstractSharedListener() {
    }

    public final void fillContext(String dataId, String group) {
        this.dataId = dataId;
        this.group = group;
    }

    public final void receiveConfigInfo(String configInfo) {
        this.innerReceive(this.dataId, this.group, configInfo);
    }

    public Executor getExecutor() {
        return null;
    }

    public abstract void innerReceive(String var1, String var2, String var3);
}

Nacos配置信息发生了变化,要怎样通知程序,我的配置发生了变化呢?答案是通过事件驱动来实现的,关键是在监听器的回调中发布了事件:

NacosContextRefresher.this.applicationContext.publishEvent(new RefreshEvent(this, (Object)null, "Refresh Nacos config"));

可以看到,当配置信息发生变化后,就会发布一个RefreshEvent事件,而RefreshEventListener会监听到该事件进行处理:

	public void onApplicationEvent(ApplicationEvent event) {
		if (event instanceof ApplicationReadyEvent) {
			handle((ApplicationReadyEvent) event);
		}
		else if (event instanceof RefreshEvent) {
			handle((RefreshEvent) event);
		}
	}
public void handle(RefreshEvent event) {
   if (this.ready.get()) { // don't handle events before app is ready
      log.debug("Event received " + event.getEventDesc());
      Set<String> keys = this.refresh.refresh();
      log.info("Refresh keys changed: " + keys);
   }
}

重点是这一行代码

Set<String> keys = this.refresh.refresh();

它是委托给ContextRefresher#refresh进行处理

	public synchronized Set<String> refresh() {
		Set<String> keys = refreshEnvironment();
		this.scope.refreshAll();
		return keys;
	}
public synchronized Set<String> refreshEnvironment() {
   // 修改之前的配置
   Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());
   // 更新配置
   updateEnvironment();
   // 发生变化的配置
   Set<String> keys = changes(before, extract(this.context.getEnvironment().getPropertySources())).keySet();
   // 发布EnvironmentChangeEvent事件
   this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
   return keys;
}

修改之前的配置
在这里插入图片描述

发生变更的key,因为我只修改了test.msg这个key对应的值
在这里插入图片描述

拿到了发生变化的key之后,就执行刷新逻辑,也就是这一行代码

		this.scope.refreshAll();

来到RefreshScope#refreshAll

	@ManagedOperation(description = "Dispose of the current instance of all beans "
			+ "in this scope and force a refresh on next method execution.")
	public void refreshAll() {
		super.destroy();
		this.context.publishEvent(new RefreshScopeRefreshedEvent());
	}

根据描述,也可以知道,它会销毁当前所有bean的实例,并且在下一次方法执行的时候,强制刷新(也就是用新的配置,创建bean)

这样也就清晰了:

  1. 配置信息发生变化时,销毁bean
  2. 再次调用时,使用新的配置创建bean,配置信息也就实现了热部署

服务端配置更新请参考:https://www.jianshu.com/p/acb9b1093a54

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值