文章将跟踪Spring-Cloud的源码,分析Nacos是如何将远程的配置文件加载到Spring的Environment中的
NacosConfigBootstrapConfiguration
在spring-cloud-starter-alibaba-nacos-config.jar包中的META-INF/spring.factories
被定义,会被自动加载
NacosConfigBootstrapConfiguration
是一个BootstrapConfiguration
BootstrapConfiguration
由BootstrapImportSelectorConfiguration
所加载
而BootstrapImportSelectorConfiguration
由BootstrapApplicationListener
加载
这一块属于Spring-Cloud的源码知识,就不深究了
NacosConfigBootstrapConfiguration是一个Configuration,自然有向容器中注册Bean的能力
public class NacosConfigBootstrapConfiguration {
// ...
@Bean
public NacosPropertySourceLocator nacosPropertySourceLocator(
NacosConfigManager nacosConfigManager) {
return new NacosPropertySourceLocator(nacosConfigManager);
}
}
NacosConfigBootstrapConfiguration
向容器中导入了NacosPropertySourceLocator
这一点非常重要,因为PropertySourceBootstrapConfiguration
会用上NacosPropertySourceLocator
PropertySourceBootstrapConfiguration
是一个ApplicationContextInitializer
PropertySourceBootstrapConfiguration
在spring-cloud-context.jar包中的META-INF/spring.factories
被定义,会被自动加载
public class PropertySourceBootstrapConfiguration implements
ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
@Autowired(required = false)
private List<PropertySourceLocator> propertySourceLocators = new ArrayList<>();
// ...
}
可以看到,PropertySourceBootstrapConfiguration
依赖Spring注入PropertySourceLocator
而NacosPropertySourceLocator
就会被注入到propertySourceLocators
属性中
SpringBoot准备好Environment和ApplicationContext后会回调ApplicationContextInitializer
的initialize
方法
整个核心逻辑就在PropertySourceBootstrapConfiguration
的initialize
方法中
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
// 1. 提前保存environment属性, 等会需要对environment添加属性
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
// 2. 调用locator的locateCollection方法
Collection<PropertySource<?>> source = locator.locateCollection(environment);
// ...
}
// ....
}
先不向下分析,而是去看看NacosPropertySourceLocator
的locateCollection
方法
locateCollection
方法的实现在PropertySourceLocator
接口中
static Collection<PropertySource<?>> locateCollection(PropertySourceLocator locator,
Environment environment) {
// 调用子类的locate方法
PropertySource<?> propertySource = locator.locate(environment);
if (propertySource == null) {
return Collections.emptyList();
}
// 如果是 { CompositePropertySource } 类型, CompositePropertySource本身就是一个集合
if (CompositePropertySource.class.isInstance(propertySource)) {
Collection<PropertySource<?>> sources = ((CompositePropertySource) propertySource)
.getPropertySources();
List<PropertySource<?>> filteredSources = new ArrayList<>();
for (PropertySource<?> p : sources) {
if (p != null) {
filteredSources.add(p);
}
}
return filteredSources;
}
else {
// 别的类型, 包装成集合后返回
return Arrays.asList(propertySource);
}
}
继续跟踪locate
方法
@Override
public PropertySource<?> locate(Environment env) {
// 1. { ConfigService } 用于读取nacos远程配置内容的, 属于nacos-api.jar包中的内容
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);
// 2. 获取bootstrap.yml中的 { spring.cloud.nacos.config.name }
String name = nacosConfigProperties.getName();
// 3. 获取bootstrap.yml中的 { spring.cloud.nacos.config.prefix }
String dataIdPrefix = nacosConfigProperties.getPrefix();
// 4. 如果没有配置{ spring.cloud.nacos.config.name }
// 那么 { dataIdPrefix } 使用 { spring.cloud.nacos.config.prefix }
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = name;
}
// 5. 如果也没有配置{ spring.cloud.nacos.config.prefix }
// 那么 { dataIdPrefix } 使用 { spring.application.name }
if (StringUtils.isEmpty(dataIdPrefix)) {
dataIdPrefix = env.getProperty("spring.application.name");
}
// 6. 创建对象, 用于返回
CompositePropertySource composite = new CompositePropertySource(
NACOS_PROPERTY_SOURCE_NAME);
// 7. 根据 { spring.cloud.nacos.config.shared-configs } 加载 nacos-config
loadSharedConfiguration(composite);
// 8. 根据 { spring.cloud.nacos.config.extension-configs } 加载 nacos-config
loadExtConfiguration(composite);
// 9. 根据 { dataIdPrefix } 加载 nacos-config
loadApplicationConfiguration(composite, dataIdPrefix, nacosConfigProperties, env);
// 10. 加载完成后, 返回CompositePropertySource
return composite;
}
==================================================================
{ spring.cloud.nacos.config.shared-configs }
和{ spring.cloud.nacos.config.extension-configs }
用于加载额外的配置,应用场景比较少,且loadSharedConfiguration
和loadExtConfiguration
两个方法的逻辑和
loadApplicationConfiguration
基本相似,就不细究这两个方法了
==================================================================
熟悉Nacos-Config都应该知道,nacos-config
由两部分组成
- dataId
- group
而接下来从Nacos-Config获取配置的dataId就由dataIdPrefix
作为前缀组成的
dataIdPrefix
取值逻辑如下
接下来继续跟踪loadApplicationConfiguration
方法
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
// 1. 获取bootstrap.yml中的 { spring.cloud.nacos.config.fileExtension }
// 如果没有配置, 默认值为: "properties"
String fileExtension = properties.getFileExtension();
// 2. 获取bootstrap.yml中的 { spring.cloud.nacos.config.group }
// 如果没有配置, 默认值为: "DEFAULT_GROUP"
String nacosGroup = properties.getGroup();
// 3. 通过 { dataId = dataIdPrefix , group = nacosGroup } 加载naocs配置
loadNacosDataIfPresent(compositePropertySource, dataIdPrefix, nacosGroup,
fileExtension, true);
// 4. 通过 { dataId = dataIdPrefix.fileExtension , group = nacosGroup } 加载naocs配置
loadNacosDataIfPresent(compositePropertySource,
dataIdPrefix + DOT + fileExtension, nacosGroup, fileExtension, true);
// 5. 通过 { dataId = dataIdPrefix-activeProfile.fileExtension , group = nacosGroup } 加载naocs配置
for (String profile : environment.getActiveProfiles()) {
String dataId = dataIdPrefix + SEP1 + profile + DOT + fileExtension;
loadNacosDataIfPresent(compositePropertySource, dataId, nacosGroup,
fileExtension, true);
}
}
3.4.5都调用了同一个方法loadNacosDataIfPresent
其区别就在于,dataId
不同
private void loadNacosDataIfPresent(final CompositePropertySource composite,
final String dataId, final String group, String fileExtension,
boolean isRefreshable) {
// 1. 调用loadNacosPropertySource方法
NacosPropertySource propertySource = this.loadNacosPropertySource(dataId, group,
fileExtension, isRefreshable);
// 2. 将 { propertySource } 添加到 { composite } 中, 先有个印象, 分析完第一步可以再回来看
// ps: 之前说越往下优先级越高: 就是因为这里采取的是头插法
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);
}
}
// 委托给了 { nacosPropertySourceBuilder }
return nacosPropertySourceBuilder.build(dataId, group, fileExtension,
isRefreshable);
}
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
// 1. 调用 configServier加载远程配置
Map<String, Object> data = loadNacosData(dataId, group, fileExtension);
// ... 下面的待会再看
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
data, new Date(), isRefreshable);
NacosPropertySourceRepository.collectNacosPropertySource(nacosPropertySource);
return nacosPropertySource;
}
private Map<String, Object> loadNacosData(String dataId, String group,
String fileExtension) {
// 1. 核心: 调用 { configService } 通过 { dataId } 和 { group } 获取配置信息
String data = configService.getConfig(dataId, group, timeout);
// 2. 将字符串转换为 { Map }
Map<String, Object> dataMap = NacosDataParserHandler.getInstance()
.parseNacosData(data, fileExtension);
// 3. 返回 { Map }
return dataMap == null ? EMPTY_MAP : dataMap;
}
================================================================
loadNacosData
通过configServer加载远程配置
这个并不是长轮询,只是一个http请求,请求指定dataId和group对应的内容
这块属于Nacos的源码,在本章中就不继续深究了
================================================================
那么继续回到NacosPropertySourceBuilder#build
方法
接着往下分析
NacosPropertySource build(String dataId, String group, String fileExtension,
boolean isRefreshable) {
// 1. 调用 configServier加载远程配置
Map<String, Object> data = loadNacosData(dataId, group, fileExtension);
// 上面的已经分析过了
// =====================================================================
// 2. 将Map包装成NacosPropertySource
NacosPropertySource nacosPropertySource = new NacosPropertySource(group, dataId,
data, new Date(), isRefreshable);
// 3. 返回
return nacosPropertySource;
}
configService
加载的远程配置包装成Map
Map
包装成NacosPropertySource
nacosPropertySource
, 将会被添加到 composite
集合中,回顾loadNacosDataIfPresent============================================================
NacosPropertySource
实现了PropertySource
接口
实际上,Spring的Environment就是多个PropertySource
的集合体
该方法返回的NacosPropertySource
待会就会被放入到Environment中
============================================================
现在,我们已经分析完了,Spring是如何获取到Nacos配置的
接下来,将继续分析,从Nacos获取到的配置,是如何放入Spring的Environment中的
NacosPropertySource
放入到composite
集合中,并且将composite
集合返回composite
集合返回给PropertySourceBootstrapConfiguration
的initialize
方法中
那么我们回到initialize
方法,接着往下看未分析的代码
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
Collection<PropertySource<?>> source = locator.locateCollection(environment);
// 上面是已经分析完的
// ================================================================
List<PropertySource<?>> sourceList = new ArrayList<>();
// 1. source集合, 将PropertySource封装成BootstrapPropertySource
// 2. 放入到sourceList集合中
for (PropertySource<?> p : source) {
sourceList.add(new BootstrapPropertySource<>(p));
}
// 3. 放入到composite集合中
composite.addAll(sourceList);
empty = false;
}
if (!empty) {
MutablePropertySources propertySources = environment.getPropertySources();
// 4. 核心方法: 之前说过 { environment } 就是多个 { propertySource } 的集合
// { insertPropertySources } 方法就是将 { composite } 集合放入到 { propertySources } 集合中
insertPropertySources(propertySources, composite);
// ...
}
}
insertPropertySources
的方法细节就不去深究了,其目的就是将将composite
集合放入到propertySources
集合中
propertySources
来自environment
,修改了propertySources
就相当于修改了environment
至此:如何从Nacos读取配置放入到Spring的Environment的源码分析就结束了