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