前言:
作为配置中心,热更新功能是必不可少的,Nacos 配置中心当然也是支持热更新功能的,上一篇我们分析了 Nacos 配置中心配置是如何加载的,本篇我们一起从源码的角度来分析一下 Nacos 配置中心的热更新源码。
Nacos 系列文章传送门:
Nacos 配置管理模型 – 命名空间(Namespace)、配置分组(Group)和配置集ID(Data ID)
Nacos Server 是如何通知 Nacos Client 服务下线?
Nacos Client 是如何接受 Nacos Server 推送的数据?
Nacos 故障转移源码分析(FailoverReactor)
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());
}
如有不正确的地方请各位指出纠正。