springcloud中nacos加载配置文件流程源码分析
在spring体系中,配置的概念非常重要,无论是spring xml配置,还是springboot中yml/properties配置,以及spring cloud体系中的配置中心,都脱离不了spring 的配置框架,区别是配置的存储格式不同,存储位置不一样。不熟悉spring配置体系的可以参考:https://mp.weixin.qq.com/s/gTSHekcN427jZ5H1LPfBFg。本文使用nacos版本为2.0.3。
调用入口:PropertySourceBootstrapConfiguration
在spring cloud体系中,配置文件的加载入口是 org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration
,该类实现了ApplicationContextInitializer接口,该接口的调用位置是:SpringApplication#run -》prepareContext -》applyInitializers,在springboot应用启动流程中调用。我们来看下PropertySourceBootstrapConfiguration的实现。initialize方法源码如下:
@Override public void initialize(ConfigurableApplicationContext applicationContext) { // 1.拿到所有的propertySourceLocator,进行排序 List<PropertySource<?>> composite = new ArrayList<>(); AnnotationAwareOrderComparator.sort(this.propertySourceLocators); boolean empty = true; ConfigurableEnvironment environment = applicationContext.getEnvironment(); // 2. 循环调用propertySourceLocator,进行配置文件的读取 for (PropertySourceLocator locator : this.propertySourceLocators) { Collection<PropertySource<?>> source = locator.locateCollection(environment); if (source == null || source.size() == 0) { continue; } List<PropertySource<?>> sourceList = new ArrayList<>(); for (PropertySource<?> p : source) { if (p instanceof EnumerablePropertySource) { EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) p; sourceList.add(new BootstrapPropertySource<>(enumerable)); } else { sourceList.add(new SimpleBootstrapPropertySource(p)); } } logger.info("Located property source: " + sourceList); composite.addAll(sourceList); empty = false; } if (!empty) { // 3. 将配置信息设置到environment中 MutablePropertySources propertySources将配置信息 = environment中.getPropertySources(); String logConfig = environment.resolvePlaceholders("${logging.config:}"); LogFile logFile = LogFile.get(environment); for (PropertySource<?> p : environment.getPropertySources()) { if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { propertySources.remove(p.getName()); } } insertPropertySources(propertySources, composite); reinitializeLoggingSystem(environment, logConfig, logFile); setLogLevels(applicationContext, environment); handleIncludedProfiles(environment); } }
从上面源码中可以看出,该方法主要有三个步骤:
- 拿到所有的propertySourceLocator,进行排序
- 循环调用propertySourceLocator,进行配置文件的读取
- 将配置信息设置到environment中
由于我们使用nacos作为配置中心,所以目前这里的propertySourceLocator集合只有NacosPropertySourceLocator一个实现类,该类位于com.alibaba.cloud.nacos.client
包下。
nacos配置加载入口:NacosPropertySourceLocator#locate方法
该方法的源码如下:
@Override public PropertySource<?> locate(Environment env) { nacosConfigProperties.setEnvironment(env); // 获取远程配置服务 ConfigService configService = nacosConfigManager.getConfigService(); if (null == configService) { log.warn("no instance of config service found, can't load config from nacos"); return null; } long timeout = nacosConfigProperties.getTimeout(); nacosPropertySourceBuilder = new NacosPropertySourceBuilder(configService, timeout); String name = nacosConfigProperties.getName(); String dataIdPrefix = nacosConfigProperties.getPrefix(); if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = name; } if (StringUtils.isEmpty(dataIdPrefix)) { dataIdPrefix = env.getProperty("spring.application.name"); } CompositePropertySource composite = new CompositePropertySource( NACOS_PROPERTY_SOURCE_NAME); // 从共享配置中读取 loadSharedConfiguration(composite); // 从扩展配置中读取 loadExtConfiguration(composite); // 读取当前应用的配置信息 loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env); return composite; }
该方法内部逻辑比较清晰,我们主要看最后几行代码,这几行是加载具体配置的入口,由于nacos的配置文件区分共享配置和应用配置,且应用配置优先级比共享配置高,加载顺序如下:
- 从共享配置中读取
- 从扩展配置中读取
- 读取当前应用的配置信息
我们以加载共享配置为例,下面是本类的调用链路,主要是做数据校验。
// 获取共享配置,并检查 private void loadSharedConfiguration( CompositePropertySource compositePropertySource) { List<NacosConfigProperties.Config> sharedConfigs = nacosConfigProperties .getSharedConfigs(); if (!CollectionUtils.isEmpty(sharedConfigs)) { checkConfiguration(sharedConfigs, "shared-configs"); loadNacosConfiguration(compositePropertySource, sharedConfigs); } } // 循环加载 private void loadNacosConfiguration(final CompositePropertySource composite, List<NacosConfigProperties.Config> configs) { for (NacosConfigProperties.Config config : configs) { loadNacosDataIfPresent(composite, config.getDataId(), config.getGroup(), NacosDataParserHandler.getInstance() .getFileExtension(config.getDataId()), config.isRefresh()); } } // 作数据校验,继续加载 private void loadNacosDataIfPresent(final CompositePropertySource composite, final String dataId, final String group, String fileExtension, boolean isRefreshable) { if (null == dataId || dataId.trim().length() < 1) { return; } if (null == group || group.trim().length() < 1) { return; } NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group, fileExtension, isRefreshable); this.addFirstPropertySource(composite, propertySource, false); } 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 这个方法。
加载&解析:NacosPropertySourceBuilder#build
当前方法的参数是nacos的概念,关于dataId和group概念可以参考官网介绍,这里直接给出示例:
dataId : xxxxx.yaml
group: DEFAULT_GROUP
fileExtension: yaml
源码如下:
/** * @param dataId Nacos dataId * @param group Nacos group */ NacosPropertySource build(String dataId, String group, String fileExtension, boolean isRefreshable) { // 加载配置 List<PropertySource<?>> propertySources = loadNacosData(dataId, group, fileExtension); NacosPropertySource nacosPropertySource = new NacosPropertySource(propertySources, group, dataId, new Date(), isRefreshable); // 缓存配置 NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource); return nacosPropertySource; } private List<PropertySource<?>> loadNacosData(String dataId, String group, String fileExtension) { String data = null; try { // 从nacos服务上读取数据 data = configService.getConfig(dataId, group, timeout); if (StringUtils.isEmpty(data)) { log.warn( "Ignore the empty nacos configuration and get it based on dataId[{}] & group[{}]", dataId, group); return Collections.emptyList(); } if (log.isDebugEnabled()) { log.debug(String.format( "Loading nacos data, dataId: '%s', group: '%s', data: %s", dataId, group, data)); } // 解析数据 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(); }
build方法:
- 读取配置
- 缓存配置
loadNacosData方法:
- 从nacos服务读取数据
- 解析字符串数据,形成PropertySourc列表
从naocs服务上读取:NacosConfigService#getConfigInner
下面我们来看最后的读取逻辑:
- 优先有本地缓存文件中读取,文件路径例如:
`
- 再从nacos服务读取,使用grpc交互
- 最后兜底机制,使用快照
private String getConfigInner(String tenant, String dataId, String group, long timeoutMs) throws NacosException { group = blank2defaultGroup(group); ParamUtils.checkKeyParam(dataId, group); ConfigResponse cr = new ConfigResponse(); cr.setDataId(dataId); cr.setTenant(tenant); cr.setGroup(group); // use local config first String content = LocalConfigInfoProcessor.getFailover(worker.getAgentName(), dataId, group, tenant); if (content != null) { LOGGER.warn("[{}] [get-config] get failover ok, dataId={}, group={}, tenant={}, config={}", worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content)); cr.setContent(content); String encryptedDataKey = LocalEncryptedDataKeyProcessor .getEncryptDataKeyFailover(agent.getName(), dataId, group, tenant); cr.setEncryptedDataKey(encryptedDataKey); configFilterChainManager.doFilter(null, cr); content = cr.getContent(); return content; } try { ConfigResponse response = worker.getServerConfig(dataId, group, tenant, timeoutMs, false); 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={}", worker.getAgentName(), dataId, group, tenant, ioe.toString()); } // 兜底机制,使用本地快照缓存 LOGGER.warn("[{}] [get-config] get snapshot ok, dataId={}, group={}, tenant={}, config={}", worker.getAgentName(), dataId, group, tenant, ContentUtils.truncateContent(content)); content = LocalConfigInfoProcessor.getSnapshot(worker.getAgentName(), 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; }
参考:
本文来自博客园,作者:mingshan,转载请注明原文链接:https://www.cnblogs.com/mingshan/p/17850858.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· DeepSeek “源神”启动!「GitHub 热点速览」
· 上周热点回顾(2.17-2.23)