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);
    }
}

上述代码逻辑说明如下。

  1. 首先this.propertySourceLocators,表示所有实现了PropertySourceLocators接口的实现类

  2. 根据默认的 AnnotationAwareOrderComparator 排序规则对propertySourceLocators数组进行排序。

  3. 获取运行的环境上下文ConfigurableEnvironment

  4. 遍历propertySourceLocators时调用

    • locate 方法,传入获取的上下文environment
    • 将source添加到PropertySource的链表中
    • 设置source是否为空的标识标量empty
  5. 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;
    }
}

上述代码的实现

  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) {
    //默认的扩展名为: 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集合。

从而实现动态配置的自动注入。

原文地址:http://www.cnblogs.com/snail-gao/p/16795821.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性