理解了上述Environment的基本原理后,如何从远程服务器上加载配置到Spring的Environment中。

NacosPropertySourceLocator

顺着前面的分析思路,我们很自然的去找PropertySourceLocator的实现类,发现除了我们自定义的GpJsonPropertySourceLocator以外,还有另外一个实现类NacosPropertySourceLocator.

于是,直接来看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);
//获取name属性,
String name = this.nacosConfigProperties.getName();
//在Spring Cloud中,默认的name=spring.application.name。
String dataIdPrefix = this.nacosConfigProperties.getPrefix();
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
} if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name"); //获取spring.application.name,赋值给dataIdPrefix
}
//创建一个Composite属性源,可以包含多个PropertySource
CompositePropertySource composite = new CompositePropertySource("NACOS");
this.loadSharedConfiguration(composite); //加载共享配置
//加载扩展配置
loadExtConfiguration(composite);
//加载自身配置
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
return composite;
}
}

上述代码的实现不难理解

  1. 获取nacos客户端的配置属性,并生成dataId(这个很重要,要定位nacos的配置)
  2. 分别调用三个方法从加载配置属性源,保存到composite组合属性源中

loadApplicationConfiguration

我们可以先不管加载共享配置、扩展配置的方法,最终本质上都是去远程服务上读取配置,只是传入的参数不一样。

  • fileExtension,表示配置文件的扩展名
  • nacosGroup表示分组
  • 加载dataid=项目名称的配置
  • 加载dataid=项目名称+扩展名的配置
  • 遍历当前配置的激活点(profile),分别循环加载带有profile的dataid配置
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
String fileExtension = properties.getFileExtension(); //默认的扩展名为: properties
String nacosGroup = properties.getGroup(); //获取group
//加载`dataid=项目名称`的配置
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
//加载`dataid=项目名称+扩展名`的配置
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
// 遍历profile(可以有多个),根据profile加载配置
for (String profile : environment.getActiveProfiles()) {
//此时的dataId=${spring.application.name}.${profile}.${fileExtension}
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
} }

loadNacosDataIfPresent

调用loadNacosPropertySource加载存在的配置信息。

把加载之后的配置属性保存到CompositePropertySource中。

private void loadNacosDataIfPresent(final CompositePropertySource composite,
final String dataId, final String group, String fileExtension,
boolean isRefreshable) {
//如果dataId为空,或者group为空,则直接跳过
if (null == dataId || dataId.trim().length() < 1) {
return;
}
if (null == group || group.trim().length() < 1) {
return;
}
//从nacos中获取属性源
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
fileExtension, isRefreshable);
//把属性源保存到compositePropertySource中
this.addFirstPropertySource(composite, propertySource, false);
}

loadNacosPropertySource

private NacosPropertySource loadNacosPropertySource(final String dataId,
final String group, String fileExtension, boolean isRefreshable) {
if (NacosContextRefresher.getRefreshCount() != 0) {
if (!isRefreshable) { //是否支持自动刷新,// 如果不支持自动刷新配置则自动从缓存获取返回(不从远程服务器加载)
return NacosPropertySourceRepository.getNacosPropertySource(dataId,
group);
}
}
//构造器从配置中心获取数据
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}

NacosPropertySourceBuilder.build

NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
//调用loadNacosData加载远程数据
List<PropertySource<?>> propertySources = loadNacosData(dataId, group,
fileExtension);
//构造NacosPropertySource(这个是Nacos自定义扩展的PropertySource,和我们前面演示的自定义PropertySource类似)。
// 相当于把从远程服务器获取的数据保存到NacosPropertySource中。
NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources,
group, dataId, new Date(), isRefreshable);
//把属性缓存到本地缓存
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}

NacosPropertySourceBuilder.loadNacosData

这个方法,就是连接远程服务器去获取配置数据的实现,关键代码是configService.getConfig

private List<PropertySource<?>> loadNacosData(String dataId, String group,
String fileExtension) {
String data = null;
try {
data = configService.getConfig(dataId, group, timeout); //加载Nacos配置数据
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 e) {
log.error("get data from Nacos error,dataId:{} ", dataId, e);
}
catch (Exception e) {
log.error("parse data from Nacos error,dataId:{},data:{}", dataId, data, e);
}
return Collections.emptyList();
}

阶段性总结

通过上述分析,我们知道了Spring Cloud集成Nacos时的关键路径,并且知道在启动时,Spring Cloud会从Nacos Server中加载动态数据保存到Environment集合。

从而实现动态配置的自动注入。

Nacos客户端的数据的加载流程

配置数据的最终加载,是基于 configService.getConfig,Nacos提供的SDK来实现的。

public String getConfig(String dataId, String group, long timeoutMs) throws NacosException

关于Nacos SDK的使用教程: https://nacos.io/zh-cn/docs/sdk.html

也就是说,接下来我们的源码分析,直接进入到Nacos这个范畴。

NacosConfigService.getConfig

@Override
public String getConfig(String dataId, String group, long timeoutMs) throws NacosException {
return getConfigInner(namespace, dataId, group, timeoutMs);
}
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException {
group = blank2defaultGroup(group); //获取group,如果为空,则为default-group
ParamUtils.checkKeyParam(dataId, group); //验证请求参数
ConfigResponse cr = new ConfigResponse(); //设置响应结果 cr.setDataId(dataId);
cr.setTenant(tenant);
cr.setGroup(group); // 优先使用本地配置
String content = LocalConfigInfoProcessor.getFailover(agent.getName(), dataId, group, tenant);
if (content != null) { //如果本地缓存中的内容不为空 LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
dataId, group, tenant, ContentUtils.truncateContent(content));
cr.setContent(content); //把内容设置到cr中。
//获取容灾配置的encryptedDataKey
String encryptedDataKey = LocalEncryptedDataKeyProcessor
.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
cr.setEncryptedDataKey(encryptedDataKey); //保存到cr
configFilterChainManager.doFilter(null, cr); //执行过滤(目前好像没有实现)
content = cr.getContent(); //返回文件content
return content;
}
//如果本地文件中不存在相关内容,则发起远程调用
try {
ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs);
//把响应内容返回
cr.setContent(response.getContent());
cr.setEncryptedDataKey(response.getEncryptedDataKey()); configFilterChainManager.doFilter(null, cr);
content = cr.getContent(); return content;
} catch (NacosException ioe) {
if (NacosException.NO_RIGHT == ioe.getErrCode()) {
throw ioe;
}
LOGGER.warn("[{}] [get-config] get from server error, dataId={}, group={}, tenant={}, msg={}",
agent.getName(), dataId, group, tenant, ioe.toString());
}
//如果出现NacosException,且不是403异常,则尝试通过本地的快照文件去获取配置进行返回。
LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", agent.getName(),
dataId, group, tenant, ContentUtils.truncateContent(content));
content = LocalConfigInfoProcessor.getSnapshot(agent.getName(), dataId, group, tenant);
cr.setContent(content);
String encryptedDataKey = LocalEncryptedDataKeyProcessor
.getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant);
cr.setEncryptedDataKey(encryptedDataKey);
configFilterChainManager.doFilter(null, cr);
content = cr.getContent();
return content;
}

从本地缓存读取配置

默认情况下,nacos先从本地缓存的配置中读取文件:C:\Users\mayn\nacos\config\fixed-192.168.8.133_8848-6a382560-ed4c-414c-a5e2-9d72c48f1a0e_nacos

如果本地缓存内容存在,则返回内容数据,否则返回空值。

public static String getFailover(String serverName, String dataId, String group, String tenant) {
File localPath = getFailoverFile(serverName, dataId, group, tenant);
if (!localPath.exists() || !localPath.isFile()) {
return null;
} try {
return readFile(localPath);
} catch (IOException ioe) {
LOGGER.error("[" + serverName + "] get failover error, " + localPath, ioe);
return null;
}
}

从指定文件目录下读取文件内容。

static File getFailoverFile(String serverName, String dataId, String group, String tenant) {
File tmp = new File(LOCAL_SNAPSHOT_PATH, serverName + "_nacos");
tmp = new File(tmp, "data");
if (StringUtils.isBlank(tenant)) {
tmp = new File(tmp, "config-data");
} else {
tmp = new File(tmp, "config-data-tenant");
tmp = new File(tmp, tenant);
}
return new File(new File(tmp, group), dataId);
}

ClientWorker.getServerConfig

ClientWorker,表示客户端的一个工作类,它负责和服务端交互。

public ConfigResponse getServerConfig(String dataId, String group, String tenant, long readTimeout)
throws NacosException {
ConfigResponse configResponse = new ConfigResponse();
if (StringUtils.isBlank(group)) { //如果group为空,则返回默认group
group = Constants.DEFAULT_GROUP;
} HttpRestResult<String> result = null;
try {
Map<String, String> params = new HashMap<String, String>(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);
}
//发起远程调用
result = agent.httpGet(Constants.CONFIG_CONTROLLER_PATH, null, params, agent.getEncode(), readTimeout);
} catch (Exception ex) {
String message = String
.format("[%s] [sub-server] get server config exception, dataId=%s, group=%s, tenant=%s",
agent.getName(), dataId, group, tenant);
LOGGER.error(message, ex);
throw new NacosException(NacosException.SERVER_ERROR, ex);
}
//根据响应结果实现不同的处理
switch (result.getCode()) {
case HttpURLConnection.HTTP_OK: //如果响应成功,保存快照到本地,并返回响应内容
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, result.getData());
configResponse.setContent(result.getData());
String configType; //配置文件的类型,如text、json、yaml等
if (result.getHeader().getValue(CONFIG_TYPE) != null) {
configType = result.getHeader().getValue(CONFIG_TYPE);
} else {
configType = ConfigType.TEXT.getType();
}
configResponse.setConfigType(configType); //设置到configResponse中,后续要根据文件类型实现不同解析策略
//获取加密数据的key
String encryptedDataKey = result.getHeader().getValue(ENCRYPTED_DATA_KEY);
//保存
LocalEncryptedDataKeyProcessor
.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, encryptedDataKey);
configResponse.setEncryptedDataKey(encryptedDataKey);
return configResponse;
case HttpURLConnection.HTTP_NOT_FOUND: //如果返回404, 清空本地快照
LocalConfigInfoProcessor.saveSnapshot(agent.getName(), dataId, group, tenant, null);
LocalEncryptedDataKeyProcessor.saveEncryptDataKeySnapshot(agent.getName(), dataId, group, tenant, null);
return configResponse;
case HttpURLConnection.HTTP_CONFLICT: {
LOGGER.error(
"[{}] [sub-server-error] get server config being modified concurrently, dataId={}, group={}, "
+ "tenant={}", agent.getName(), dataId, group, tenant);
throw new NacosException(NacosException.CONFLICT,
"data being modified, dataId=" + dataId + ",group=" + group + ",tenant=" + tenant);
}
case HttpURLConnection.HTTP_FORBIDDEN: {
LOGGER.error("[{}] [sub-server-error] no right, dataId={}, group={}, tenant={}", agent.getName(),
dataId, group, tenant);
throw new NacosException(result.getCode(), result.getMessage());
}
default: {
LOGGER.error("[{}] [sub-server-error] dataId={}, group={}, tenant={}, code={}", agent.getName(),
dataId, group, tenant, result.getCode());
throw new NacosException(result.getCode(),
"http error, code=" + result.getCode() + ",dataId=" + dataId + ",group=" + group + ",tenant="
+ tenant);
}
}
}

ServerHttpAgent.httpGet

发起远程请求的实现。

@Override
public HttpRestResult<String> httpGet(String path, Map<String, String> headers, Map<String, String> paramValues,
String encode, long readTimeoutMs) throws Exception {
final long endTime = System.currentTimeMillis() + readTimeoutMs;
injectSecurityInfo(paramValues); //注入安全信息
String currentServerAddr = serverListMgr.getCurrentServerAddr();//获取当前服务器地址
int maxRetry = this.maxRetry; //获取最大重试次数,默认3次
//配置HttpClient的属性,默认的readTimeOut超时时间是3s
HttpClientConfig httpConfig = HttpClientConfig.builder()
.setReadTimeOutMillis(Long.valueOf(readTimeoutMs).intValue())
.setConTimeOutMillis(ConfigHttpClientManager.getInstance().getConnectTimeoutOrDefault(100)).build();
do { try {
//设置header
Header newHeaders = getSpasHeaders(paramValues, encode);
if (headers != null) {
newHeaders.addAll(headers);
}
//构建query查询条件
Query query = Query.newInstance().initParams(paramValues);
//发起http请求,http://192.168.8.133:8848/nacos/v1/cs/configs
HttpRestResult<String> result = NACOS_RESTTEMPLATE
.get(getUrl(currentServerAddr, path), httpConfig, newHeaders, query, String.class);
if (isFail(result)) { //如果请求失败,
LOGGER.error("[NACOS ConnectException] currentServerAddr: {}, httpCode: {}",
serverListMgr.getCurrentServerAddr(), result.getCode());
} else {
// Update the currently available server addr
serverListMgr.updateCurrentServerAddr(currentServerAddr);
return result;
}
} catch (ConnectException connectException) {
LOGGER.error("[NACOS ConnectException httpGet] currentServerAddr:{}, err : {}",
serverListMgr.getCurrentServerAddr(), connectException.getMessage());
} catch (SocketTimeoutException socketTimeoutException) {
LOGGER.error("[NACOS SocketTimeoutException httpGet] currentServerAddr:{}, err : {}",
serverListMgr.getCurrentServerAddr(), socketTimeoutException.getMessage());
} catch (Exception ex) {
LOGGER.error("[NACOS Exception httpGet] currentServerAddr: " + serverListMgr.getCurrentServerAddr(),
ex);
throw ex;
}
//如果服务端列表有多个,并且当前请求失败,则尝试用下一个地址进行重试
if (serverListMgr.getIterator().hasNext()) {
currentServerAddr = serverListMgr.getIterator().next();
} else {
maxRetry--; //重试次数递减
if (maxRetry < 0) {
throw new ConnectException(
"[NACOS HTTP-GET] The maximum number of tolerable server reconnection errors has been reached");
}
serverListMgr.refreshCurrentServerAddr();
} } while (System.currentTimeMillis() <= endTime); LOGGER.error("no available server");
throw new ConnectException("no available server");
}

Nacos Server端的配置获取

客户端向服务端加载配置,调用的接口是:/nacos/v1/cs/configs , 于是,在Nacos的源码中找到该接口

定位到Nacos源码中的ConfigController.getConfig中的方法,代码如下:

@GetMapping
@Secured(action = ActionTypes.READ, parser = ConfigResourceParser.class)
public void getConfig(HttpServletRequest request, HttpServletResponse response,
@RequestParam("dataId") String dataId, @RequestParam("group") String group,
@RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant,
@RequestParam(value = "tag", required = false) String tag)
throws IOException, ServletException, NacosException {
// check tenant
ParamUtils.checkTenant(tenant);
tenant = NamespaceUtil.processNamespaceParameter(tenant); //租户,也就是namespaceid
// check params
ParamUtils.checkParam(dataId, group, "datumId", "content"); //检查请求参数是否为空
ParamUtils.checkParam(tag); final String clientIp = RequestUtil.getRemoteIp(request); //获取请求的ip
inner.doGetConfig(request, response, dataId, group, tenant, tag, clientIp); //加载配置
}

inner.doGetConfig

public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group,
String tenant, String tag, String clientIp) throws IOException, ServletException {
final String groupKey = GroupKey2.getKey(dataId, group, tenant);
String autoTag = request.getHeader("Vipserver-Tag");
String requestIpApp = RequestUtil.getAppName(request); //请求端的应用名称 int lockResult = tryConfigReadLock(groupKey); //尝试获取当前请求配置的读锁(避免读写冲突) final String requestIp = RequestUtil.getRemoteIp(request); //请求端的ip boolean isBeta = false;
//lockResult>0 ,表示CacheItem(也就是缓存的配置项)不为空,并且已经加了读锁,意味着这个缓存数据不能被删除。
//lockResult=0 ,表示cacheItem为空,不需要加读锁
//lockResult=01 , 表示加锁失败,存在冲突。
//下面这个if,就是针对这三种情况进行处理。
if (lockResult > 0) {
// LockResult > 0 means cacheItem is not null and other thread can`t delete this cacheItem
FileInputStream fis = null;
try {
String md5 = Constants.NULL;
long lastModified = 0L;
//从本地缓存中,根据groupKey获取CacheItem
CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey);
//判断是否是beta发布,也就是测试版本
if (cacheItem.isBeta() && cacheItem.getIps4Beta().contains(clientIp)) {
isBeta = true;
}
//获取配置文件的类型
final String configType =
(null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType();
response.setHeader("Config-Type", configType);
//返回文件类型的枚举对象
FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(configType);
String contentTypeHeader = fileTypeEnum.getContentType();
response.setHeader(HttpHeaderConsts.CONTENT_TYPE, contentTypeHeader); File file = null;
ConfigInfoBase configInfoBase = null;
PrintWriter out = null;
if (isBeta) { //如果是测试配置
md5 = cacheItem.getMd54Beta();
lastModified = cacheItem.getLastModifiedTs4Beta();
if (PropertyUtil.isDirectRead()) {
configInfoBase = persistService.findConfigInfo4Beta(dataId, group, tenant);
} else {
file = DiskUtil.targetBetaFile(dataId, group, tenant); //从磁盘中获取文件,得到的是一个完整的File
}
response.setHeader("isBeta", "true");
} else {
if (StringUtils.isBlank(tag)) { //判断tag标签是否为空,tag对应的是nacos配置中心的标签选项
if (isUseTag(cacheItem, autoTag)) {
if (cacheItem.tagMd5 != null) {
md5 = cacheItem.tagMd5.get(autoTag);
}
if (cacheItem.tagLastModifiedTs != null) {
lastModified = cacheItem.tagLastModifiedTs.get(autoTag);
}
if (PropertyUtil.isDirectRead()) {
configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, autoTag);
} else {
file = DiskUtil.targetTagFile(dataId, group, tenant, autoTag);
} response.setHeader("Vipserver-Tag",
URLEncoder.encode(autoTag, StandardCharsets.UTF_8.displayName()));
} else {//直接走这个逻辑(默认不会配置tag属性)
md5 = cacheItem.getMd5(); //获取缓存的md5
lastModified = cacheItem.getLastModifiedTs(); //获取最后更新时间
if (PropertyUtil.isDirectRead()) { //判断是否是stamdalone模式且使用的是derby数据库,如果是,则从derby数据库加载数据
configInfoBase = persistService.findConfigInfo(dataId, group, tenant);
} else {
//否则,如果是数据库或者集群模式,先从本地磁盘得到文件
file = DiskUtil.targetFile(dataId, group, tenant);
}
//如果本地磁盘文件为空,并且configInfoBase为空,则表示配置数据不存在,直接返回null
if (configInfoBase == null && fileNotExist(file)) {
// FIXME CacheItem
// No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp); // pullLog.info("[client-get] clientIp={}, {},
// no data",
// new Object[]{clientIp, groupKey}); response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().println("config data not exist");
return HttpServletResponse.SC_NOT_FOUND + "";
}
}
} else {//如果tag不为空,说明配置文件设置了tag标签
if (cacheItem.tagMd5 != null) {
md5 = cacheItem.tagMd5.get(tag);
}
if (cacheItem.tagLastModifiedTs != null) {
Long lm = cacheItem.tagLastModifiedTs.get(tag);
if (lm != null) {
lastModified = lm;
}
}
if (PropertyUtil.isDirectRead()) {
configInfoBase = persistService.findConfigInfo4Tag(dataId, group, tenant, tag);
} else {
file = DiskUtil.targetTagFile(dataId, group, tenant, tag);
}
if (configInfoBase == null && fileNotExist(file)) {
// FIXME CacheItem
// No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1,
ConfigTraceService.PULL_EVENT_NOTFOUND, -1, requestIp); // pullLog.info("[client-get] clientIp={}, {},
// no data",
// new Object[]{clientIp, groupKey}); response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().println("config data not exist");
return HttpServletResponse.SC_NOT_FOUND + "";
}
}
}
//把获取的数据结果设置到response中返回 response.setHeader(Constants.CONTENT_MD5, md5); // Disable cache.
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-cache,no-store");
if (PropertyUtil.isDirectRead()) {
response.setDateHeader("Last-Modified", lastModified);
} else {
fis = new FileInputStream(file);
response.setDateHeader("Last-Modified", file.lastModified());
}
//如果是单机模式,直接把数据写回到客户端
if (PropertyUtil.isDirectRead()) {
out = response.getWriter();
out.print(configInfoBase.getContent());
out.flush();
out.close();
} else {//否则,通过trasferTo
fis.getChannel()
.transferTo(0L, fis.getChannel().size(), Channels.newChannel(response.getOutputStream()));
} LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, requestIp, md5, TimeUtils.getCurrentTimeStr()); final long delayed = System.currentTimeMillis() - lastModified; // TODO distinguish pull-get && push-get
/*
Otherwise, delayed cannot be used as the basis of push delay directly,
because the delayed value of active get requests is very large.
*/
ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified,
ConfigTraceService.PULL_EVENT_OK, delayed, requestIp); } finally {
releaseConfigReadLock(groupKey); //释放锁
IoUtils.closeQuietly(fis);
}
} else if (lockResult == 0) { //说明缓存为空, // FIXME CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1.
ConfigTraceService
.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT_NOTFOUND, -1,
requestIp); response.setStatus(HttpServletResponse.SC_NOT_FOUND);
response.getWriter().println("config data not exist");
return HttpServletResponse.SC_NOT_FOUND + ""; } else {// PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); response.setStatus(HttpServletResponse.SC_CONFLICT);
response.getWriter().println("requested file is being modified, please try later.");
return HttpServletResponse.SC_CONFLICT + ""; } return HttpServletResponse.SC_OK + "";
}

persistService.findConfigInfo

从derby数据库中获取数据内容,这个就是一个基本的数据查询操作。

@Override
public ConfigInfo findConfigInfo(final String dataId, final String group, final String tenant) {
final String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant;
final String sql = "SELECT ID,data_id,group_id,tenant_id,app_name,content,md5,type FROM config_info "
+ " WHERE data_id=? AND group_id=? AND tenant_id=?";
final Object[] args = new Object[] {dataId, group, tenantTmp};
return databaseOperate.queryOne(sql, args, CONFIG_INFO_ROW_MAPPER); }

DiskUtil.targetFile

从磁盘目录中获取目标文件,直接根据dataId/group/tenant ,查找指定目录下的文件即可

public static File targetFile(String dataId, String group, String tenant) {
File file = null;
if (StringUtils.isBlank(tenant)) {
file = new File(EnvUtil.getNacosHome(), BASE_DIR);
} else {
file = new File(EnvUtil.getNacosHome(), TENANT_BASE_DIR);
file = new File(file, tenant);
}
file = new File(file, group);
file = new File(file, dataId);
return file;
}

至此,NacosPropertySourceLocator 完成了从Nacos Server上动态获取配置并缓存到本地,从而实现Nacos动态配置获取的能力!

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mic带你学架构

如果本篇文章对您有帮助,还请帮忙点个关注和赞,您的坚持是我不断创作的动力。欢迎关注「跟着Mic学架构」公众号公众号获取更多技术干货!

Spring Cloud Nacos实现动态配置加载的源码分析的更多相关文章

  1. Springboot 加载配置文件源码分析

    Springboot 加载配置文件源码分析 本文的分析是基于springboot 2.2.0.RELEASE. 本篇文章的相关源码位置:https://github.com/wbo112/blogde ...

  2. Springboot学习04-默认错误页面加载机制源码分析

    Springboot学习04-默认错误页面加载机制源码分析 前沿 希望通过本文的学习,对错误页面的加载机制有这更神的理解 正文 1-Springboot错误页面展示 2-Springboot默认错误处 ...

  3. ElasticSearch 启动时加载 Analyzer 源码分析

    ElasticSearch 启动时加载 Analyzer 源码分析 本文介绍 ElasticSearch启动时如何创建.加载Analyzer,主要的参考资料是Lucene中关于Analyzer官方文档 ...

  4. 微服务架构 | *2.3 Spring Cloud 启动及加载配置文件源码分析(以 Nacos 为例)

    目录 前言 1. Spring Cloud 什么时候加载配置文件 2. 准备 Environment 配置环境 2.1 配置 Environment 环境 SpringApplication.prep ...

  5. springboot Properties加载顺序源码分析

    关于properties: 在spring框架中properties为Environment对象重要组成部分, springboot有如下几种种方式注入(优先级从高到低): 1.命令行 java -j ...

  6. jQuery实现DOM加载方法源码分析

    传统的判断dom加载的方法 使用 dom0级 onload事件来进行触发所有浏览器都支持在最初是很流行的写法 我们都熟悉这种写法: window.onload=function(){ ... }  但 ...

  7. Spring boot加载REACTIVE源码分析

    一,加载REACTIVE相关自动配置 spring boot通过判断含org.springframework.web.reactive.DispatcherHandler字节文件就确定程序类型是REA ...

  8. spring启动component-scan类扫描加载过程---源码分析

    http://blog.csdn.net/xieyuooo/article/details/9089441#comments

  9. Spring加载流程源码分析03【refresh】

      前面两篇文章分析了super(this)和setConfigLocations(configLocations)的源代码,本文来分析下refresh的源码, Spring加载流程源码分析01[su ...

随机推荐

  1. Android官方文档翻译 八 2.1Setting Up the Action Bar

    Setting Up the Action Bar 建立Action Bar This lesson teaches you to 这节课教给你 Support Android 3.0 and Abo ...

  2. Docsify部署IIS

    什么是Docsify? 一个神奇的文档网站生成器.docsify 可以快速帮你生成文档网站.不同于 GitBook.Hexo 的地方是它不会生成静态的 .html 文件,所有转换工作都是在运行时.如果 ...

  3. 字节一面:事务还没提交的时候,redolog 能不能被持久化到磁盘呢?

    又是被自己菜醒的一天,总结面经看到这题目听都没听过,打开百度就像吃饭一样自然 老规矩,背诵版在文末.点击阅读原文可以直达我收录整理的各大厂面试真题 首先,咱需要明白的是,啥是持久化? 听起来高大上,换 ...

  4. 【解决了一个小问题】alert manager中的cluster.advertise-address参数是什么意思?

    如果在启动 alert manager的时候,不填写参数: /usr/bin/alertmanager --config.file=/etc/alert_manager/alertmanager.ya ...

  5. 【分享】让prometheus支持PUSH模式,可以使用remote write协议推送数据

    2021-10-21补充: 我通过修改源码来让prometheus支持remote-write,纯属多此一举: --enable-feature=remote-write-receiver这个命令行参 ...

  6. golang中字符串、数值、2进制、8进制、16进制、10进制、日期和字符串之间的转换

    package main import ( "fmt" "reflect" "strconv" "time" ) fun ...

  7. How to find out which process is listening upon a port

    When we covered port scanning a short while ago we discovered how to tell which ports had processes ...

  8. .NET 7 预览版来啦,我升级体验了

    听说.NET 7 来了,站长怎能不尝鲜呢,在除夕当天将体验情况简单汇报下,然后迎新春喽: 本文目录 .NET 7 详情(Proposed .NET 7 Breaking Changes #7131) ...

  9. nohub命令简单介绍。

    /* 来自对 https://www.cnblogs.com/kexianting/p/11628983.html 编辑. 一 什么是 nohub? 1 是 no hang up 的缩写,就是不挂断的 ...

  10. c语言中数组的定义和java中数组定义的一些区别

    感谢原文:https://blog.csdn.net/gzwdz778/article/details/79799408 一维情况下: c中,数组的声明需要给出数组的维数,比如: int arr[5] ...