Bootstrap

Nacos 配置中心 Client 端配置热更新源码分析

前言:

作为配置中心,热更新功能是必不可少的,Nacos 配置中心当然也是支持热更新功能的,上一篇我们分析了 Nacos 配置中心配置是如何加载的,本篇我们一起从源码的角度来分析一下 Nacos 配置中心的热更新源码。

Nacos 系列文章传送门:

Nacos 初步认识和 Nacos 部署细节

Nacos 配置管理模型 – 命名空间(Namespace)、配置分组(Group)和配置集ID(Data ID)

Nacos 注册中心和配置中心【实战】

服务启动何时触发 Nacos 的注册流程?

Nacos Client 端服务注册流程源码分析

Nacos Server 端服务注册流程源码分析

Nacos 服务发现(订阅)源码分析(客户端)

Nacos 服务发现(订阅)源码分析(服务端)

Nacos Server 是如何通知 Nacos Client 服务下线?

Nacos Client 是如何接受 Nacos Server 推送的数据?

Nacos 故障转移源码分析(FailoverReactor)

Nacos 集群数据同步源码分析

NacosContextRefresher 注册监听器源码解析

Nacos Server 端配置发生变化了,客户端如何感知配置发生了变化呢?Nacos 使用了监听器的做法,NacosContextRefresher 类会监听应用启动发布的 ApplicationReadyEvent 事件,并注册监听。


//NacosContextRefresher#onApplicationEvent
//com.alibaba.cloud.nacos.refresh.NacosContextRefresher#onApplicationEvent
public void onApplicationEvent(ApplicationReadyEvent event) {
	//ready 原子类 默认 false  进行 CAS 更新
	if (this.ready.compareAndSet(false, true)) {
		//注册监听 
		this.registerNacosListenersForApplications();
	}

}

NacosContextRefresher 完成了监听器的注册,NacosContextRefresher 何时加载的?

NacosContextRefresher 何时加载的?

我们查看 Nacos Config 的 META-INF/spring.factoies 中发现有一个叫做 NacosConfigAutoConfiguration 的类,很明显这个类跟 Nacos 配置中心肯定有极大关系,NacosConfigAutoConfiguration 源码如下:


package com.alibaba.cloud.nacos;

import com.alibaba.cloud.nacos.refresh.NacosContextRefresher;
import com.alibaba.cloud.nacos.refresh.NacosRefreshHistory;
import com.alibaba.cloud.nacos.refresh.NacosRefreshProperties;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnProperty(
    name = {"spring.cloud.nacos.config.enabled"},
    matchIfMissing = true
)
public class NacosConfigAutoConfiguration {
    public NacosConfigAutoConfiguration() {
    }

	//Nacos 刷新属性
    @Bean
    public NacosConfigProperties nacosConfigProperties(ApplicationContext context) {
        return context.getParent() != null && BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context.getParent(), NacosConfigProperties.class).length > 0 ? (NacosConfigProperties)BeanFactoryUtils.beanOfTypeIncludingAncestors(context.getParent(), NacosConfigProperties.class) : new NacosConfigProperties();
    }

	//Nacos 属性属性
    @Bean
    public NacosRefreshProperties nacosRefreshProperties() {
        return new NacosRefreshProperties();
    }

	//Nacos 刷新历史
    @Bean
    public NacosRefreshHistory nacosRefreshHistory() {
        return new NacosRefreshHistory();
    }
	
	//Nacos 配置管理
    @Bean
    public NacosConfigManager nacosConfigManager(NacosConfigProperties nacosConfigProperties) {
        return new NacosConfigManager(nacosConfigProperties);
    }

	//Nacos 上下文刷新 重点关注
    @Bean
    public NacosContextRefresher nacosContextRefresher(NacosConfigManager nacosConfigManager, NacosRefreshHistory nacosRefreshHistory) {
        return new NacosContextRefresher(nacosConfigManager, nacosRefreshHistory);
    }
}

Nacos Config 的 META-INF/spring.factoies 文件内容如下:

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 Config 的 META-INF/spring.factoies 文件中找到了 NacosConfigAutoConfiguration 类,因此 NacosConfigAutoConfiguration 类是通过 Spring Boot 自动配置装载的,而 NacosConfigAutoConfiguration 类中注入了 NacosContextRefresher Bean,因此 NacosContextRefresher 的加载就了然了。

NacosContextRefresher#registerNacosListenersForApplications 方法源码解析

NacosContextRefresher#registerNacosListenersForApplications 方法会判断是否启动了自动刷新配置,然后获取所有的 Nacos 配置,并判断每个配置是否可以刷新,如果可以刷新则注册响应的监听。

//com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListenersForApplications
private void registerNacosListenersForApplications() {
	//是否启动配置刷新
	if (this.isRefreshEnabled()) {
		//迭代遍历获取 Nacos 配置
		Iterator var1 = NacosPropertySourceRepository.getAll().iterator();

		while(var1.hasNext()) {
			//Nacos 属性配置
			NacosPropertySource propertySource = (NacosPropertySource)var1.next();
			//配置是否可刷新判断
			if (propertySource.isRefreshable()) {
				//获取dataId
				String dataId = propertySource.getDataId();
				//注册监听
				this.registerNacosListener(propertySource.getGroup(), dataId);
			}
		}
	}

}


NacosContextRefresher#registerNacosListener 方法源码解析

NacosContextRefresher#registerNacosListener 方法主要就是注册监听器,其主要操作就是刷新记录加一,增加 Nacos 刷新记录,发布 RefreshEvent 事件,然后调用 NacosConfigService#addListener 注册监听器。

//com.alibaba.cloud.nacos.refresh.NacosContextRefresher#registerNacosListener
private void registerNacosListener(final String groupKey, final 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) {
				//Nacos 刷新记录+1
				NacosContextRefresher.refreshCountIncrement();
				//添加 Nacos 刷新历史记录
				NacosContextRefresher.this.nacosRefreshHistory.addRefreshRecord(dataId, group, configInfo);
				//发布 Nacos 刷新事件
				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);
	}

}


NacosConfigService#addListener 方法源码解析

NacosConfigService#addListener 方法只是调用了 ClientWorker#addTenantListeners 注册监听器。


//com.alibaba.nacos.client.config.NacosConfigService#addListener
public void addListener(String dataId, String group, Listener listener) throws NacosException {
	this.worker.addTenantListeners(dataId, group, Arrays.asList(listener));
}

ClientWorker#addTenantListeners 方法源码解析

ClientWorker#addTenantListeners 方法会创建一个 CacheData 对象,用来管理监听器,CacheData 对象本身也会加入本地缓存。

public void addTenantListeners(String dataId, String group, List<? extends Listener> listeners)
		throws NacosException {
	//获取分组
	group = blank2defaultGroup(group);
	//获取租户
	String tenant = agent.getTenant();
	//从缓存中获取 CacheData 如果获取不到就创建一个 并更新缓存  重点关注
	CacheData cache = addCacheDataIfAbsent(dataId, group, tenant);
	//遍历监听器
	for (Listener listener : listeners) {
		//监听器添加到缓存中
		cache.addListener(listener);
	}
}


NacosPropertySourceBuilder#addCacheDataIfAbsent 方法源码解析

ClientWorker#addCacheDataIfAbsent 方法先从 cacheMap 中获取 CacheData 对象,如果 cacheMap 中没有,就创建一个 CacheData 对象,并加入到 cacheMap 缓存中,至此需要热更新的配置都注册了监听器,一旦远程配置中心的配置发生了变化,对应的监听器就会做响应的处理。


//com.alibaba.nacos.client.config.impl.ClientWorker#addCacheDataIfAbsent
public CacheData addCacheDataIfAbsent(String dataId, String group, String tenant) throws NacosException {
	//获取key
	String key = GroupKey.getKeyTenant(dataId, group, tenant);
	//从缓存中获取 CacheData
	CacheData cacheData = cacheMap.get(key);
	//CacheData 为空判断
	if (cacheData != null) {
		//不为空 直接返回
		return cacheData;
	}
	//为空则创建一个
	cacheData = new CacheData(configFilterChainManager, agent.getName(), dataId, group, tenant);
	// multiple listeners on the same dataid+group and race condition
	//比较并替换 保证一个 CacheData 只有一个且最新
	CacheData lastCacheData = cacheMap.putIfAbsent(key, cacheData);
	//再次为空判断
	if (lastCacheData == null) {
		//fix issue # 1317
		if (enableRemoteSyncConfig) {
			ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);
			cacheData.setContent(response.getContent());
		}
		//任务id
		int taskId = cacheMap.size() / (int) ParamUtil.getPerTaskConfigSize();
		cacheData.setTaskId(taskId);
		lastCacheData = cacheData;
	}
	
	// reset so that server not hang this check
	lastCacheData.setInitializing(true);
	
	LOGGER.info("[{}] [subscribe] {}", agent.getName(), key);
	MetricsMonitor.getListenConfigCountMonitor().set(cacheMap.size());
	
	return lastCacheData;
}


配置更新

ClientWorker 构造方法源码解析

ClientWorker 构造方法中创建了三个线程池,如下:

  • executor: 单线程线程池,作用未知,欢迎各位大佬指点。
  • executorService:处理长轮训请求的线程池。
  • executor: 配置变更检查的线程池(重点关注)。

//com.alibaba.nacos.client.config.impl.ClientWorker#ClientWorker
@SuppressWarnings("PMD.ThreadPoolCreationRule")
public ClientWorker(final HttpAgent agent, final ConfigFilterChainManager configFilterChainManager,
		final Properties properties) {
	//HttpAgent
	this.agent = agent;
	//ConfigFilterChainManager
	this.configFilterChainManager = configFilterChainManager;
	
	// Initialize the timeout parameter
	//初始化超时参数 默认超时时间是 30 秒 重试时间 20 秒
	init(properties);
	//单线程调度线程池
	this.executor = Executors.newScheduledThreadPool(1, new ThreadFactory() {
		@Override
		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() {
				@Override
				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;
				}
			});
	//延迟调度线程池 延迟 1 毫秒  10毫秒调度一次
	this.executor.scheduleWithFixedDelay(new Runnable() {
		@Override
		public void run() {
			try {
				//检查配置变更
				checkConfigInfo();
			} catch (Throwable e) {
				LOGGER.error("[" + agent.getName() + "] [sub-check] rotate check error", e);
			}
		}
	}, 1L, 10L, TimeUnit.MILLISECONDS);
}


ClientWorker#checkConfigInfo 方法源码解析

ClientWorker#checkConfigInfo 方法的作用是提交长轮训任务。


//com.alibaba.nacos.client.config.impl.ClientWorker#checkConfigInfo
public void checkConfigInfo() {
	// Dispatch tasks.
	//获取 cacheMap 中的任务数量 前面加入的
	int listenerSize = cacheMap.size();
	// Round up the longingTaskCount.
	//当前需要处理的任务数量 四舍五入
	int longingTaskCount = (int) Math.ceil(listenerSize / ParamUtil.getPerTaskConfigSize());
	//需要处理的任务书大于 0 判断 currentLongingTaskCount 默认是 0
	if (longingTaskCount > currentLongingTaskCount) {
		for (int i = (int) currentLongingTaskCount; i < longingTaskCount; i++) {
			//交由线程池处理任务 也就是提交长轮训任务 LongPollingRunnable  重点关注
			executorService.execute(new LongPollingRunnable(i));
		}
		//处理完毕后将 longingTaskCount 赋值给 currentLongingTaskCount
		currentLongingTaskCount = longingTaskCount;
	}
}

LongPollingRunnable#run 方法源码解析

LongPollingRunnable#run 方法主要是长轮训任务的处理过程,分别处理了故障转移和正常状态的两种情况,会先向 Nacos Server 发送请求获取发生配置变更的 key,然后回根据获取到的 key 再次发送请求到 Nacos Server 获取最新配置信息,最后比较配置的 MD5 值是否有变化,如果有变化就发送通知。


//com.alibaba.nacos.client.config.impl.ClientWorker.LongPollingRunnable#run
@Override
public void run() {
	//cacheData 我们上面看到过的 管理监听器对象
	List<CacheData> cacheDatas = new ArrayList<CacheData>();
	//初始化缓存列表
	List<String> inInitializingCacheList = new ArrayList<String>();
	try {
		// check failover config
		//循环处理 故障转移配置 cacheMap
		for (CacheData cacheData : cacheMap.values()) {
			//taskId 判断
			if (cacheData.getTaskId() == taskId) {
				//加入到 cacheDatas 中
				cacheDatas.add(cacheData);
				try {
					//检查本地配置
					checkLocalConfig(cacheData);
					//是否使用本地配置信息
					if (cacheData.isUseLocalConfigInfo()) {
						//检查监听器的 md5 值是否一致
						cacheData.checkListenerMd5();
					}
				} catch (Exception e) {
					LOGGER.error("get local config info error", e);
				}
			}
		}
		
		// check server config
		//Nacos Server 的配置 最终会发起请求到 Nacos Server 端 获取发生变更了的配置 key
		List<String> changedGroupKeys = checkUpdateDataIds(cacheDatas, inInitializingCacheList);
		if (!CollectionUtils.isEmpty(changedGroupKeys)) {
			LOGGER.info("get changedGroupKeys:" + changedGroupKeys);
		}
		//遍历发生变更了的配置 key
		for (String groupKey : changedGroupKeys) {
			//key 的组成 dataId+group+tenant
			String[] key = GroupKey.parseKey(groupKey);
			String dataId = key[0];
			String group = key[1];
			String tenant = null;
			//判断租户的逻辑
			if (key.length == 3) {
				tenant = key[2];
			}
			try {
				//发送请求到 Nacos Server 获取配置 这里的逻辑和服务启动获取配置的逻辑一致
				ConfigResponse response = getServerConfig(dataId, group, tenant, 3000L);
				//获取 cacheMap 中的 CacheData
				CacheData cache = cacheMap.get(GroupKey.getKeyTenant(dataId, group, tenant));
				// FIXME temporary fix https://github.com/alibaba/nacos/issues/7039
				//设置响应内容 其实就是配置信息
				cache.setEncryptedDataKey(response.getEncryptedDataKey());
				cache.setContent(response.getContent());
				//ConfigType 设置
				if (null != response.getConfigType()) {
					cache.setType(response.getConfigType());
				}
				LOGGER.info("[{}] [data-received] dataId={}, group={}, tenant={}, md5={}, content={}, type={}",
						agent.getName(), dataId, group, tenant, cache.getMd5(),
						ContentUtils.truncateContent(response.getContent()), response.getConfigType());
			} catch (NacosException ioe) {
				String message = String
						.format("[%s] [get-update] get changed config exception. dataId=%s, group=%s, tenant=%s",
								agent.getName(), dataId, group, tenant);
				LOGGER.error(message, ioe);
			}
		}
		//遍历 cacheDatas
		for (CacheData cacheData : cacheDatas) {
			if (!cacheData.isInitializing() || inInitializingCacheList
					.contains(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant))) {
				//校验 MD5 是否有变化 如果有变化 就发送通知
				cacheData.checkListenerMd5();
				//设置没有在初始化
				cacheData.setInitializing(false);
			}
		}
		//初始化列表 清空
		inInitializingCacheList.clear();

		executorService.execute(this);
		
	} catch (Throwable e) {
		
		// If the rotation training task is abnormal, the next execution time of the task will be punished
		LOGGER.error("longPolling error : ", e);
		//任务重试
		executorService.schedule(this, taskPenaltyTime, TimeUnit.MILLISECONDS);
	}
}


ClientWorker#checkUpdateDataIds 方法源码解析

ClientWorker#checkUpdateDataIds 方法会把 dataId 和 group 按一定的规则拼接成一个字符串,调用 ClientWorker#checkUpdateConfigStr 方法,获取配置变更的 dataId,只拉取改动过的配置信息,这可以过滤掉绝大部分没有配置项,提升效率。


//com.alibaba.nacos.client.config.impl.ClientWorker#checkUpdateDataIds
List<String> checkUpdateDataIds(List<CacheData> cacheDatas, List<String> inInitializingCacheList) throws Exception {
	StringBuilder sb = new StringBuilder();
	//dataId group 拼接字符串
	for (CacheData cacheData : cacheDatas) {
		if (!cacheData.isUseLocalConfigInfo()) {
			sb.append(cacheData.dataId).append(WORD_SEPARATOR);
			sb.append(cacheData.group).append(WORD_SEPARATOR);
			if (StringUtils.isBlank(cacheData.tenant)) {
				sb.append(cacheData.getMd5()).append(LINE_SEPARATOR);
			} else {
				sb.append(cacheData.getMd5()).append(WORD_SEPARATOR);
				sb.append(cacheData.getTenant()).append(LINE_SEPARATOR);
			}
			//cacheData 是否正在初始化
			if (cacheData.isInitializing()) {
				//当 cacheData 第一次出现在 cacheMap 中时会更新
				// It updates when cacheData occurs in cacheMap by first time.
				inInitializingCacheList
						.add(GroupKey.getKeyTenant(cacheData.dataId, cacheData.group, cacheData.tenant));
			}
		}
	}
	//inInitializingCacheList 是否为空
	boolean isInitializingCacheList = !inInitializingCacheList.isEmpty();
	//调用 Nacos Server
	return checkUpdateConfigStr(sb.toString(), isInitializingCacheList);
}

ClientWorker#checkUpdateConfigStr 方法源码解析

ClientWorker#checkUpdateConfigStr 方法主要就是组装参数发送请求到 Nacos Server 端获取配置发生变化了的配置信息。


//com.alibaba.nacos.client.config.impl.ClientWorker#checkUpdateConfigStr
List<String> checkUpdateConfigStr(String probeUpdateString, boolean isInitializingCacheList) throws Exception {
	//构造请求参数
	Map<String, String> params = new HashMap<String, String>(2);
	params.put(Constants.PROBE_MODIFY_REQUEST, probeUpdateString);
	Map<String, String> headers = new HashMap<String, String>(2);
	headers.put("Long-Pulling-Timeout", "" + timeout);
	
	// told server do not hang me up if new initializing cacheData added in
	//如果正在初始化缓存数据 告诉服务器不要挂断 No-Hangup
	if (isInitializingCacheList) {
		headers.put("Long-Pulling-Timeout-No-Hangup", "true");
	}
	//probeUpdateString 为空判断
	if (StringUtils.isBlank(probeUpdateString)) {
		return Collections.emptyList();
	}
	
	try {
		// In order to prevent the server from handling the delay of the client's long task,
		// increase the client's read timeout to avoid this problem.
		//超时时间
		long readTimeoutMs = timeout + (long) Math.round(timeout >> 1);
		//发送 HTTP 请求到 Nacos Server 路径: /v1/cs/configs/listener
		HttpRestResult<String> result = agent
				.httpPost(Constants.CONFIG_CONTROLLER_PATH + "/listener", headers, params, agent.getEncode(),
						readTimeoutMs);
		//结果判断
		if (result.ok()) {
			//设置为健康的 server
			setHealthServer(true);
			//解析响应的数据
			return parseUpdateDataIdResponse(result.getData());
		} else {
			//返回结果不是 OK 的 设置为不健康的 server
			setHealthServer(false);
			LOGGER.error("[{}] [check-update] get changed dataId error, code: {}", agent.getName(),
					result.getCode());
		}
	} catch (Exception e) {
		//异常结果也设置为不健康的 server
		setHealthServer(false);
		LOGGER.error("[" + agent.getName() + "] [check-update] get changed dataId exception", e);
		throw e;
	}
	return Collections.emptyList();
}

CacheData#checkListenerMd5 方法源码解析

CacheData#checkListenerMd5 方法主要作用就是检查配置的 MD5 值是否相同,如果不同则调用 CacheData#safeNotifyListener 方法处理。

//com.alibaba.nacos.client.config.impl.CacheData#checkListenerMd5
void checkListenerMd5() {
	//遍历所有 MD5 值
	for (ManagerListenerWrap wrap : listeners) {
		//比较 MD5 值是否相同
		if (!md5.equals(wrap.lastCallMd5)) {
			//MD5 值不同发送通知
			safeNotifyListener(dataId, group, content, type, md5, encryptedDataKey, wrap);
		}
	}
}


CacheData#safeNotifyListener 方法源码解析

CacheData#safeNotifyListener 方法主要就是调用监听器的 receiveConfigInfo() 方法,然后更新 ManagerListenerWrap 中的 lastContent、lastCallMd5 字段。


//com.alibaba.nacos.client.config.impl.CacheData#safeNotifyListener
private void safeNotifyListener(final String dataId, final String group, final String content, final String type,
		final String md5, final String encryptedDataKey, final ManagerListenerWrap listenerWrap) {
	//获取监听器
	final Listener listener = listenerWrap.listener;

	Runnable job = new Runnable() {
		@Override
		public void run() {
			//当前线程类的 类加载器
			ClassLoader myClassLoader = Thread.currentThread().getContextClassLoader();
			//监听器类的 类加载器
			ClassLoader appClassLoader = listener.getClass().getClassLoader();
			try {
				//监听器类型判断
				if (listener instanceof AbstractSharedListener) {
					//是共享监听器类型
					AbstractSharedListener adapter = (AbstractSharedListener) listener;
					//填充上下文
					adapter.fillContext(dataId, group);
					LOGGER.info("[{}] [notify-context] dataId={}, group={}, md5={}", name, dataId, group, md5);
				}
				// 执行回调之前先将线程classloader设置为具体webapp的classloader,以免回调方法中调用spi接口是出现异常或错用(多应用部署才会有该问题)。
				Thread.currentThread().setContextClassLoader(appClassLoader);
				//配置响应对象
				ConfigResponse cr = new ConfigResponse();
				//属性设置
				cr.setDataId(dataId);
				cr.setGroup(group);
				cr.setContent(content);
				cr.setEncryptedDataKey(encryptedDataKey);
				configFilterChainManager.doFilter(null, cr);
				String contentTmp = cr.getContent();
				//监听器接受配置信息
				listener.receiveConfigInfo(contentTmp);
				
				// compare lastContent and content
				if (listener instanceof AbstractConfigChangeListener) {
					//解析变更数据
					Map data = ConfigChangeHandler.getInstance()
							.parseChangeData(listenerWrap.lastContent, content, type);
					//配置改变事件
					ConfigChangeEvent event = new ConfigChangeEvent(data);
					//接受配置更改事件 会触发监听器的回调事件
					((AbstractConfigChangeListener) listener).receiveConfigChange(event);
					listenerWrap.lastContent = content;
				}
				//MD5 赋值
				listenerWrap.lastCallMd5 = md5;
				LOGGER.info("[{}] [notify-ok] dataId={}, group={}, md5={}, listener={} ", name, dataId, group, md5,
						listener);
			} catch (NacosException ex) {
				LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} errCode={} errMsg={}",
						name, dataId, group, md5, listener, ex.getErrCode(), ex.getErrMsg());
			} catch (Throwable t) {
				LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} tx={}", name, dataId,
						group, md5, listener, t.getCause());
			} finally {
				Thread.currentThread().setContextClassLoader(myClassLoader);
			}
		}
	};
	
	final long startNotify = System.currentTimeMillis();
	try {
		//判断 监听器的执行器是否为空
		if (null != listener.getExecutor()) {
			//不为空直接交给执行器处理 也就是线程池处理
			listener.getExecutor().execute(job);
		} else {
			//为空的话 直接交给线程处理
			job.run();
		}
	} catch (Throwable t) {
		LOGGER.error("[{}] [notify-error] dataId={}, group={}, md5={}, listener={} throwable={}", name, dataId,
				group, md5, listener, t.getCause());
	}
	final long finishNotify = System.currentTimeMillis();
	LOGGER.info("[{}] [notify-listener] time cost={}ms in ClientWorker, dataId={}, group={}, md5={}, listener={} ",
			name, (finishNotify - startNotify), dataId, group, md5, listener);
}

RefreshEventListener#onApplicationEvent 方法源码解析

应用就绪事件 ApplicationReadyEvent 和 配置刷新事件 RefreshEvent 的监听器都是 RefreshEventListener,RefreshEventListener#onApplicationEvent 监听到不同的事件做出不同的逻辑处理。


//org.springframework.cloud.endpoint.event.RefreshEventListener#onApplicationEvent
public void onApplicationEvent(ApplicationEvent event) {
	//事件类型判断 
	if (event instanceof ApplicationReadyEvent) {
		//应用就绪事件
		this.handle((ApplicationReadyEvent)event);
	} else if (event instanceof RefreshEvent) {
		//配置刷新事件
		this.handle((RefreshEvent)event);
	}

}

//org.springframework.cloud.endpoint.event.RefreshEventListener#handle(org.springframework.boot.context.event.ApplicationReadyEvent)
public void handle(ApplicationReadyEvent event) {
	//cas 修改 ready 值
	this.ready.compareAndSet(false, true);
}


//org.springframework.cloud.endpoint.event.RefreshEventListener#handle(org.springframework.cloud.endpoint.event.RefreshEvent)
public void handle(RefreshEvent event) {
	//获取 ready 状态
	if (this.ready.get()) {
		log.debug("Event received " + event.getEventDesc());
		//刷新环境
		Set<String> keys = this.refresh.refresh();
		log.info("Refresh keys changed: " + keys);
	}

}

ContextRefresher#refresh 方法源码解析

ContextRefresher#refresh 方法会刷新 Spring 容器环境,实际上是会重新创建一个 ConfigurableApplicationContext,将新的 ConfigurableApplicationContext 环境信息复制到当前容器上下文,然后刷新作用域内的所有 BeanLifecycleWrapper。


//org.springframework.cloud.context.refresh.ContextRefresher#refresh
public synchronized Set<String> refresh() {
	//刷新环境 本质是重新创建一个 ConfigurableApplicationContext 将新的 ConfigurableApplicationContext 环境信息复制到 当前容器上下文
	Set<String> keys = this.refreshEnvironment();
	//刷新范围内所有 bean 的当前实例 并在下一个方法执行时强制刷新
	this.scope.refreshAll();
	return keys;
}

ContextRefresher#refreshEnvironment 方法源码解析

ContextRefresher#refreshEnvironment 方法主要作用是刷新容器环境,发布容器环境信息变更事件。

//org.springframework.cloud.context.refresh.ContextRefresher#refreshEnvironment
public synchronized Set<String> refreshEnvironment() {
	//获取当前容器中的配置信息 并转换为 map
	Map<String, Object> before = this.extract(this.context.getEnvironment().getPropertySources());
	//配置信息复制
	this.addConfigFilesToEnvironment();
	Set<String> keys = this.changes(before, this.extract(this.context.getEnvironment().getPropertySources())).keySet();
	//发布环境信息变更事件
	this.context.publishEvent(new EnvironmentChangeEvent(this.context, keys));
	return keys;
}


ContextRefresher#addConfigFilesToEnvironment 方法源码解析

ContextRefresher#addConfigFilesToEnvironment 方法主要逻辑就是创建一个 ConfigurableApplicationContext,将新的 ConfigurableApplicationContext 环境信息复制到当前容器上下文。


//org.springframework.cloud.context.refresh.ContextRefresher#addConfigFilesToEnvironment
ConfigurableApplicationContext addConfigFilesToEnvironment() {
	//应用程序上下文
	ConfigurableApplicationContext capture = null;
	boolean var15 = false;

	try {
		var15 = true;
		//先copy 当前上下文环境属性
		StandardEnvironment environment = this.copyEnvironment(this.context.getEnvironment());
		SpringApplicationBuilder builder = (new SpringApplicationBuilder(new Class[]{ContextRefresher.Empty.class})).bannerMode(Mode.OFF).web(WebApplicationType.NONE).environment(environment);
		builder.application().setListeners(Arrays.asList(new BootstrapApplicationListener(), new ConfigFileApplicationListener()));
		//创建新的应用程序上下文
		capture = builder.run(new String[0]);
		if (environment.getPropertySources().contains("refreshArgs")) {
			environment.getPropertySources().remove("refreshArgs");
		}
		//获取当前容器中环境中的配置属性
		MutablePropertySources target = this.context.getEnvironment().getPropertySources();
		String targetName = null;
		//遍历处理
		Iterator var6 = environment.getPropertySources().iterator();
		//while 循环中主要是进行配置替换和配置添加
		while(var6.hasNext()) {
			PropertySource<?> source = (PropertySource)var6.next();
			String name = source.getName();
			if (target.contains(name)) {
				targetName = name;
			}

			if (!this.standardSources.contains(name)) {
				if (target.contains(name)) {
					//配置替换
					target.replace(name, source);
				} else if (targetName != null) {
					//配置添加
					target.addAfter(targetName, source);
				} else {
					//配置添加
					target.addFirst(source);
					targetName = name;
				}
			}
		}

		var15 = false;
	} finally {
		if (var15) {
			for(ConfigurableApplicationContext closeable = capture; closeable != null; closeable = (ConfigurableApplicationContext)closeable.getParent()) {
				try {
					closeable.close();
				} catch (Exception var16) {
				}

				if (!(closeable.getParent() instanceof ConfigurableApplicationContext)) {
					break;
				}
			}

		}
	}

	for(ConfigurableApplicationContext closeable = capture; closeable != null; closeable = (ConfigurableApplicationContext)closeable.getParent()) {
		try {
			closeable.close();
		} catch (Exception var17) {
		}

		if (!(closeable.getParent() instanceof ConfigurableApplicationContext)) {
			break;
		}
	}

	return capture;
}


//org.springframework.cloud.context.refresh.ContextRefresher#copyEnvironment
private StandardEnvironment copyEnvironment(ConfigurableEnvironment input) {
	//创建环境对象
	StandardEnvironment environment = new StandardEnvironment();
	MutablePropertySources capturedPropertySources = environment.getPropertySources();
	String[] var4 = DEFAULT_PROPERTY_SOURCES;
	int var5 = var4.length;
	//复制环境数据
	for(int var6 = 0; var6 < var5; ++var6) {
		String name = var4[var6];
		if (input.getPropertySources().contains(name)) {
			if (capturedPropertySources.contains(name)) {
				capturedPropertySources.replace(name, input.getPropertySources().get(name));
			} else {
				capturedPropertySources.addLast(input.getPropertySources().get(name));
			}
		}
	}

	environment.setActiveProfiles(input.getActiveProfiles());
	environment.setDefaultProfiles(input.getDefaultProfiles());
	Map<String, Object> map = new HashMap();
	map.put("spring.jmx.enabled", false);
	map.put("spring.main.sources", "");
	map.put("spring.main.web-application-type", "NONE");
	capturedPropertySources.addFirst(new MapPropertySource("refreshArgs", map));
	return environment;
}

RefreshScope#refreshAll 方法源码解析

RefreshScope#refreshAll 方法会清空缓存中的所有 BeanLifecycleWrapper,并销毁所有的 BeanLifecycleWrapper,这样下次的 BeanLifecycleWrapper 就会被重新创建,拿到的配置属性自然就是最新的配置了。


//org.springframework.cloud.context.scope.refresh.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() {
	//空缓存中的所有 BeanLifecycleWrapper,并销毁所有的 BeanLifecycleWrapper
	super.destroy();
	//发布刷新事件 刷新作用域为 RefreshScope 的bean
	this.context.publishEvent(new RefreshScopeRefreshedEvent());
}

如有不正确的地方请各位指出纠正。

;