PropertySourceLocator加载原理
SpringApplication.run
在spring boot项目启动时,有一个prepareContext的方法,它会回调所有实现了ApplicationContextInitializer的实例,来做一些初始化工作。
public ConfigurableApplicationContext run(String... args) {
//省略代码...
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
//省略代码
return context;
}
PropertySourceBootstrapConfiguration.initialize
其中,PropertySourceBootstrapConfiguration就实现了ApplicationContextInitializer,initialize方法代码如下。
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
List<PropertySource<?>> composite = new ArrayList<>();
//对propertySourceLocators数组进行排序,根据默认的AnnotationAwareOrderComparator
AnnotationAwareOrderComparator.sort(this.propertySourceLocators);
boolean empty = true;
//获取运行的环境上下文
ConfigurableEnvironment environment = applicationContext.getEnvironment();
for (PropertySourceLocator locator : this.propertySourceLocators) {
//回调所有实现PropertySourceLocator接口实例的locate方法,并收集到source这个集合中。
Collection<PropertySource<?>> source = locator.locateCollection(environment);
//如果source为空,直接进入下一次循环
if (source == null || source.size() == 0) {
continue;
}
//遍历source,把PropertySource包装成BootstrapPropertySource加入到sourceList中。
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);
//将source添加到数组
composite.addAll(sourceList);
//表示propertysource不为空
empty = false;
}
//只有propertysource不为空的情况,才会设置到environment中
if (!empty) {
//获取当前Environment中的所有PropertySources.
MutablePropertySources propertySources = environment.getPropertySources();
String logConfig = environment.resolvePlaceholders("${logging.config:}");
LogFile logFile = LogFile.get(environment);
// 遍历移除bootstrapProperty的相关属性
for (PropertySource<?> p : environment.getPropertySources()) {
if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) {
propertySources.remove(p.getName());
}
}
//把前面获取到的PropertySource,插入到Environment中的PropertySources中。
insertPropertySources(propertySources, composite);
reinitializeLoggingSystem(environment, logConfig, logFile);
setLogLevels(applicationContext, environment);
handleIncludedProfiles(environment);
}
}
上述代码逻辑说明如下。
-
首先this.propertySourceLocators,表示所有实现了PropertySourceLocators接口的实现类
-
根据默认的 AnnotationAwareOrderComparator 排序规则对propertySourceLocators数组进行排序。
-
获取运行的环境上下文ConfigurableEnvironment
-
遍历propertySourceLocators时调用
- locate 方法,传入获取的上下文environment
- 将source添加到PropertySource的链表中
- 设置source是否为空的标识标量empty
-
source不为空的情况,才会设置到environment中
-
返回Environment的可变形式,可进行的操作如addFirst、addLast
-
移除propertySources中的bootstrapProperties
-
根据config server覆写的规则,设置propertySources
-
处理多个active profiles的配置信息
-
PS:注意:this.propertySourceLocators这个集合中的PropertySourceLocator,是通过自动装配机制完成注入的,具体的实现在BootstrapImportSelector这个类中。
ApplicationContextInitializer的理解和使用
ApplicationContextInitializer是Spring框架原有的东西, 它的主要作用就是在,ConfigurableApplicationContext
类型(或者子类型)的ApplicationContext做refresh之前,允许我们对ConfiurableApplicationContext的实例做进
一步的设置和处理。
它可以用在需要对应用程序上下文进行编程初始化的web应用程序中,比如根据上下文环境来注册
propertySource,或者配置文件。而Config 的这个配置中心的需求恰好需要这样一个机制来完成。
创建一个TestApplicationContextInitializer
public class TestApplicationContextInitializer implements
ApplicationContextInitializer<ConfigurableApplicationContext>{
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
ConfigurableEnvironment ce=applicationContext.getEnvironment();
for(PropertySource<?> propertySource:ce.getPropertySources()){
System.out.println(propertySource);
}
System.out.println("--------end");
}
}
添加spi加载
org.springframework.context.ApplicationContextInitializer= \
com.example.demo.controller.TestApplicationContextInitializer
在控制台就可以看到当前的PropertySource的输出结果。
ConfigurationPropertySourcesPropertySource {name='configurationProperties'}
StubPropertySource {name='servletConfigInitParams'}
StubPropertySource {name='servletContextInitParams'}
PropertiesPropertySource {name='systemProperties'}
OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'}
RandomValuePropertySource {name='random'}
MapPropertySource {name='springCloudClientHostInfo'}
OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/bootstrap.yaml]'}
MapPropertySource {name='springCloudDefaultProperties'}
CachedRandomPropertySource {name='cachedrandom'}
NacosPropertySourceLocator
那么如何从远程服务器上加载配置到Spring的Environment中。
PropertySourceLocator的实现类,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)) {
//获取spring.application.name,赋值给dataIdPrefix
dataIdPrefix = env.getProperty("spring.application.name");
}
//创建一个Composite属性源,可以包含多个PropertySource
CompositePropertySource composite = new CompositePropertySource("NACOS");
this.loadSharedConfiguration(composite);
//加载扩展配置
this.loadExtConfiguration(composite);
//加载自身配置
this.loadApplicationConfiguration(composite, dataIdPrefix, this.nacosConfigProperties, env);
return composite;
}
}
上述代码的实现
-
获取nacos客户端的配置属性,并生成dataId(这个很重要,要定位nacos的配置)
-
分别调用三个方法从加载配置属性源,保存到composite组合属性源中
loadApplicationConfiguration
暂且先不管加载共享配置、扩展配置的方法,最终本质上都是去远程服务上读取配置,只是传入的参数不一样。
-
fileExtension,表示配置文件的扩展名
-
nacosGroup表示分组
-
加载dataid=项目名称的配置
-
加载dataid=项目名称+扩展名的配置
-
遍历当前配置的激活点(profile),分别循环加载带有profile的dataid配置
private void loadApplicationConfiguration(
CompositePropertySource compositePropertySource, String dataIdPrefix,
NacosConfigProperties properties, Environment environment) {
//默认的扩展名为: properties
String fileExtension = properties.getFileExtension();
//获取group
String nacosGroup = properties.getGroup();
//加载`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)
//相当于把从远程服务器获取的数据保存到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);
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集合。
从而实现动态配置的自动注入。
标签:dataId,group,String,Spring,Nacos,fileExtension,加载,Cloud,name From: https://www.cnblogs.com/snail-gao/p/16795821.html