Spring Cloud Nacos如何通过源码解析实现动态配置的加载机制?

2026-05-22 15:471阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计3752个文字,预计阅读时间需要16分钟。

Spring Cloud Nacos如何通过源码解析实现动态配置的加载机制?

理解了Environment的基本原理后,要将远程服务器的配置加载到Spring的Environment中,可以通过以下步骤实现:

1. 使用NacosPropertySourceLocator进行配置源定位。

2.分析NacosPropertySourceLocator的实现思路。

首先,我们查找NacosPropertySourceLocator的实现类,通常位于Spring Cloud Alibaba Nacos的依赖中。该类负责从Nacos服务器获取配置信息,并将其注册到Spring的Environment中。

以下是简化后的步骤:

- 通过NacosPropertySourceLocator的getEnvironment方法获取配置信息。

- 分析NacosPropertySourceLocator如何与Nacos服务器交互,以及如何解析配置信息。- 实现从远程Nacos服务器加载配置到Spring Environment的逻辑。

例如,以下是一个简化的代码片段:

javaimport org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.core.env.Environment;import org.springframework.beans.factory.annotation.Autowired;import com.alibaba.nacos.api.config.NacosPropertySource;

@RefreshScopepublic class NacosConfigLoader {

@Autowired private Environment env;

public void loadConfigFromNacos() { NacosPropertySource propertySource=new NacosPropertySource(nacos-config, nacosConfigService.getNacosProperties()); env.getPropertySources().addFirst(propertySource); }}

这里,我们首先获取到Nacos服务器上的配置信息,然后将其作为NacosPropertySource添加到Spring Environment中。这样,Spring应用程序就可以在运行时从Nacos服务器动态地获取和刷新配置了。

理解了上述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

Spring Cloud Nacos如何通过源码解析实现动态配置的加载机制?

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的使用教程: 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.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学架构」公众号公众号获取更多技术干货!

本文共计3752个文字,预计阅读时间需要16分钟。

Spring Cloud Nacos如何通过源码解析实现动态配置的加载机制?

理解了Environment的基本原理后,要将远程服务器的配置加载到Spring的Environment中,可以通过以下步骤实现:

1. 使用NacosPropertySourceLocator进行配置源定位。

2.分析NacosPropertySourceLocator的实现思路。

首先,我们查找NacosPropertySourceLocator的实现类,通常位于Spring Cloud Alibaba Nacos的依赖中。该类负责从Nacos服务器获取配置信息,并将其注册到Spring的Environment中。

以下是简化后的步骤:

- 通过NacosPropertySourceLocator的getEnvironment方法获取配置信息。

- 分析NacosPropertySourceLocator如何与Nacos服务器交互,以及如何解析配置信息。- 实现从远程Nacos服务器加载配置到Spring Environment的逻辑。

例如,以下是一个简化的代码片段:

javaimport org.springframework.cloud.context.config.annotation.RefreshScope;import org.springframework.core.env.Environment;import org.springframework.beans.factory.annotation.Autowired;import com.alibaba.nacos.api.config.NacosPropertySource;

@RefreshScopepublic class NacosConfigLoader {

@Autowired private Environment env;

public void loadConfigFromNacos() { NacosPropertySource propertySource=new NacosPropertySource(nacos-config, nacosConfigService.getNacosProperties()); env.getPropertySources().addFirst(propertySource); }}

这里,我们首先获取到Nacos服务器上的配置信息,然后将其作为NacosPropertySource添加到Spring Environment中。这样,Spring应用程序就可以在运行时从Nacos服务器动态地获取和刷新配置了。

理解了上述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

Spring Cloud Nacos如何通过源码解析实现动态配置的加载机制?

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的使用教程: 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.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学架构」公众号公众号获取更多技术干货!