写于:2019-10-27 12:11:30 参考资料: Sprinb Boot 2.1.5.RELEASE 官方文档 Spring framework 5.1.9.RELEASE 官方文档 配置中心以 Spring Cloud Config 作为展开

一、前置问题

在以Spring Boot 和 Spring Cloud 作为技术栈的开发中,针对应用的配置最常见用的就是三种配置,

  • classpath 下的 application.properties
  • commandline-args :命令行参数,通过 java命令启动应用时指定的入参
  • 以及分布式系统中的分布式配置中心

假设此时:在这三个配置中,同时存在一个配置:ext-info=XX 。那么 在程序运行时,究竟那么配置会生效?

二、Spring Boot 提供外部化配置

Externalized Configuration

Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments.You can use properties files, YAML files, environment variables, and command-line arguments to externalize configuration.Property values can be injected directly into your beans by using the @Value annotation, accessed through Spring’s Environment abstraction, or be bound to structured objects through @ConfigurationProperties.

大意如下

为了使得同一套代码,能够在不同的配置环境下运行, Spring Boot 提供了多种配置外部化的能力,同时这几种配置方式能够共存。
这几种外部化配置存在的形式,包括 properties 文件、YML 文件、环境变量、命令行参数等。
在程序中,开发者可以通过 @Value 注解,直接引用 Environment 对象,或者通过 @ConfigurationProperties注解等方式对这些外部化配置进行读取,灵活的变更程序配置。

配置信息分布在不同位置不同文件中,使得配置更灵活,但必然会带来一个问题:不同配置文件中存在同一个属性配置(如:ext-info=XXX),应用程序在运行中会以哪个配置文件的配置作为其运行配置?

针对这个问题,Spring Boot 官方给出了说明文档,在多种配置文件共存的情况下,应用程序会选择哪个配置文件的属性配置作为运行配置(读取优先级:由高到低

  1. Devtools global settings properties on your home directory (~/.spring-boot-devtools.properties when devtools is active).
  2. @TestPropertySource annotations on your tests.
  3. properties attribute on your tests. Available on @SpringBootTest and the test annotations for testing a particular slice of your application.
  4. Command line arguments.
  5. Properties from SPRING_APPLICATION_JSON (inline JSON embedded in an environment variable or system property).
  6. ServletConfig init parameters.
  7. ServletContext init parameters.
  8. JNDI attributes from java:comp/env.
  9. Java System properties (System.getProperties()).
  10. OS environment variables.
  11. A RandomValuePropertySource that has properties only in random.*.
  12. Profile-specific application properties outside of your packaged jar (application-{profile}.properties and YAML variants).
  13. Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).
  14. Application properties outside of your packaged jar (application.properties and YAML variants).
  15. Application properties packaged inside your jar (application.properties and YAML variants).
  16. @PropertySource annotations on your @Configuration classes.
  17. Default properties (specified by setting SpringApplication.setDefaultProperties).

    三、实例测试其中几种常见配置方式的优先级顺序

测试的配置方式如下

  • 优先级 4 的 Command line arguments :命令行参数配置
  • 优先级 12 的 jar包外部的 application.properties
  • 优先级 13 的 jar 包内部的 application.properties(默认:classpath 下的 application.properties)

1、环境准备

  • 运行环境
    spring Boot 2.1.5.RELEASE Web 应用
  • 准备测试的属性配置
    ext-info=XXX
  • 提供 API,用来读取 ext-info 配置信息 ```java @Value(“${ext-info}”) private String extInfo;

/ http://localhost:9527/ext-info / @RequestMapping(“/ext-info”) public String extInfo(){ return extInfo; }

  1. - 通过 /actuator/env 端点,获取当前应用存在的多种配置文件信息
  2. <a name="e267edd8"></a>
  3. ### 2、优先级测试
  4. <a name="2384d7b8"></a>
  5. #### **测试一、** **【优先级 4 】Command line arguments(命令行配置)** VS **【优先级 13 】application.properties(jar 包内部默认配置文件)**
  6. - **step1、**在 jar 包内部,src/main/resource 中的 application.properties 文件中增加测试配置 主要配置
  7. ```properties
  8. # 内部配置文件
  9. ext-info = info-inside-jar
  • step2、运行 jar 程序,同时指定命令行参数 —ext-info=commandLineArgs
    java -jar ext-configuration-0.0.1-SNAPSHOT.jar —ext-info=commandLineArgs
  • step3、访问 自定义API、和 actuator env 端点

09.png

  • 结论
    Command line arguments(命令行配置) 配置属性优先级比 application.properties(jar 包内部默认配置文件)高,两者存在同一个属性配置时,以 Command line arguments(命令行配置) 配置为主。

测试二、 【优先级 13】 application.properties(jar 包内部默认配置文件)VS【优先级 12】 application-outside.properteis(jar包外部配置文件)配置 优先级

  • step1、外部配置文件 application-outside.properties 主要配置

    1. # 外部配置文件
    2. ext-info = outside-jar
  • 在 运行 jar 程序时,通过命令行参数的方式指定外部配置文件 —spring.config.location=XXX

  • step2、jar 包内配置文件 application.properteis 主要配置(同测试一)

    1. # 内部配置文件
    2. ext-info = info-inside-jar
  • step3、运行 jar 程序
    java -jar ext-configuration-0.0.1-SNAPSHOT.jar —spring.config.location=.xxxxx/application-outside.properties

  • step4、访问 自定义API、和 actuator env 端点
  • 结论
    application-outside.properteis(jar包外部配置文件) 配置属性读取优先级比 application.properties(jar 包内部默认配置文件) 高。两者存在同一配置属性时,以外部配置文件的属性配置为主。

    命令行指定了外部配置文件:jar包内的 application.properties 以及 bootstrap.properties 文件将不再被加载

测试三、【优先级 4 】 Command line arguments(命令行配置)、【优先级 13】application.properties(jar 包内部默认配置文件) 、【优先级12】 application-outside.properteis(jar包外部配置文件)配置 优先级

这里不进行测试流程展示,通过测试一和测试二能够得出,三者的优先级由高到低依次为:Command line arguments(命令行配置) > application-outside.properteis(jar包外部配置文件) > application.properties(jar 包内部默认配置文件)

更多内容,参考官方文档

四、源码分析

Command line arguments(命令行配置)和 内部配置文件 为例,分析 Spring Boot 如何实现配置读取的优先级。

前置概念

分析之前,先来看看 Spring Boot 应用的环境上下文 Environment。引用Spring Framework 官方对于 Environemtn 的说明

The Environment interface is an abstraction integrated in the container that models two key aspects of the application environment: profiles and properties.

Spring Framework 官方大意:

Environment 是一种在容器内以 配置(Profile)和属性(Properties) 为模型的应用环境抽象整合。

通俗的理解

首先,Spring 应用可以简单分为二部分:spring应用本身Spring 应用所处的环境。Spring 应用 除了本身的 IOC 容器等之外,最为重要的就是 Spring 应用的运行环境,也就是运行配置。在 Spring 中可以通过 profile(配置) 或者 properties(属性) 配置的方式构建 Environemnt 的相关信息。

  • profile
    就是配置多个环境的配置,比如:dev,test,prod等环境配置,然后通过指定某个环境,读取某个环境下的配置文件信息
  • properties(属性)
    官方说明

    Properties play an important role in almost all applications and may originate from a variety of sources: properties files, JVM system properties, system environment variables, JNDI, servlet context parameters, ad-hoc Properties objects, Map objects, and so on.

  • 大意
    properties 在几乎所有的应用当中都有着重要的作用,它可能存在多个数据源:property文件,JVM系统property,系统环境变量,JNDI,servlet上下文参数,ad-hoc属性对象,Map等。

说完概念,来简单看看代码

在 Spring Web MVC 中, Environment 抽象的默认实现类为:StandardServletEnvironment

StandardServletEnvironment 类结构如下:
02.png

通过类图结构知道所有形式的配置文件,最后都能够通过 Environment 访问读取其中的配置文件。

鉴于此,以 Environment 为入口进行配置文件读取顺序的分析!

分析一 、配置信息的获取

在 Spring Boot 应用中,可以通过 @Value @ConfigurationProperties注解 等方式来获取属性配置值。而他们本质调用的都是 Environment#getProperty 来获取配置信息。

在一个最简版的 Spring Boot Web 应用中,Environment 的实现为 StandardServletEnvironment

StandardServletEnvironment#getProperty 作为入口进行分析

StandardServletEnvironment 获取配置属性值的 UML 时序图

StandardServletEnvironment#getProperty 的方法继承自父类 AbstractEnvironment

10.png

获取配置信息的源码: PropertySourcesPropertyResolver#getProperty

  1. public class PropertySourcesPropertyResolver extends AbstractPropertyResolver {
  2. @Nullable
  3. private final PropertySources propertySources;
  4. @Nullable
  5. protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
  6. if (this.propertySources != null) {
  7. // 遍历 propertySources
  8. for (PropertySource<?> propertySource : this.propertySources) {
  9. ......
  10. // 尝试获取值,获取成功,直接返回
  11. Object value = propertySource.getProperty(key);
  12. if (value != null) {
  13. if (resolveNestedPlaceholders && value instanceof String) {
  14. value = resolveNestedPlaceholders((String) value);
  15. }
  16. logKeyFound(key, propertySource, value);
  17. return convertValueIfNecessary(value, targetValueType);
  18. }
  19. }
  20. }
  21. ......
  22. return null;
  23. }
  24. }

代码主线逻辑

  • 1、遍历 PropertySourcesPropertyResolver 中的属性值 PropertySources 中的 PropertySource 列表

    PropertySources 的默认实现为 MutablePropertySources

  • 2、调用每个 PropertySource 的 PropertySource#getProperty 获取对应的配置属性值

    PropertySource 存在多种实现,几个常见的实现:MapPropertySource(指定为Map实例的PropertySource实现),OriginTrackedMapPropertySource(classpath 下 applic ation.properteis 读取后的抽象对象)等。

深入 PropertySource#getProperty 源码查看属性获取的具体实现

OriginTrackedMapPropertySource(classpath 下 application.properteis 读取后的抽象对象) 为例

先来看看 OriginTrackedMapPropertySource 类结构

03.png
OriginTrackedMapPropertySource 继承自 MapPropertySource ,而 MapPropertySource 对于配置文件的抽象以Map对象 key-vlaue 的形式进行存储

再来看看 OriginTrackedMapPropertySource#getProperty

OriginTrackedMapPropertySource#getProperty 具体操作继承自其父类 **MapPropertySource#getProperty**

来看看源码 **MapPropertySource#getProperty**

  1. public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {
  2. public Object getProperty(String name) {
  3. return this.source.get(name);
  4. }
  5. }

通过源码可以得知,由于 MapPropertySource 对 配置文件属性的抽象是通过 Map 以 Key-Value 的方式进行存储的,而 OriginTrackedMapPropertySource 继承自 MapPropertySource ,且未重写 getProperty 方法,所以 OriginTrackedMapPropertySource 对于配置文件的抽象存储也是通过 Map 的方式进行存储,而获取配置属性值,只需要通过 Map#get 就能够获取到。

结论

不同位置的配置文件被 Spring 应用读取之后被抽象成不同的 PropertySource 对象(如 : classpath 下的 application.properties 被抽象成 OriginTrackedMapPropertySource)。

这些 PropertySource 对象组成一个 List 列表,并作为属性值放入到 Environemnt 抽象中。

在用户调用 Environemnt#getProperty 获取属性时,遍历 List 列表中所有的 PropertySource 尝试获取配置,先从哪个 PropertySource 中获取到配置值,就直接返回。

换句话说,不同配置文件抽象的 PropertySource 对象,根据不同的加载优先级被放入 List 列表中,谁在 List 列表中越靠前,相同属性配置下,哪个配置文件对应的属性读取优先级就越高。

分析二:配置读取优先级

例子:command line arg 配置读取优先级比 classpath 下 默认的 application.properties 配置优先级高的原因

提前抛出结论

从上面的分析中,能够知道,不同配置文件被抽象成 PropertySource对象(类似于Spring 中 Bean 被抽象为 BeanDefinition) ,多个不同的 PropertySource 组成一个 List 列表 在 Environment 以属性 propertySources的方式存在。而读取配置是以遍历的方式进行,所以在 List 列表中越靠前的 PropertySrouce 对应的配置,优先级越高。

换句话时候,就是 Spring Boot 将读取到的不同的配置文件放置到 List 列表的位置,来决定,配置的优先级问题。

以 SpringApplication#run 为入口,找到读取配置的代码实现

11.png

② SpringApplication#run 关键代码
  1. public class SpringApplication {
  2. // String... args :命令行入参
  3. public ConfigurableApplicationContext run(String... args) {
  4. // 将命令行入参 args 封装成 ApplicationArguments
  5. ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
  6. ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  7. }
  8. }

代码关键逻辑:

将 命令行入参 args 封装成对象 ApplicationArguments ,并作为参入, 调用 SpringApplication#prepareEnvironment

③ SpringApplication#prepareEnvironment 关键代码
  1. public class SpringApplication {
  2. private ConfigurableEnvironment prepareEnvironment(
  3. SpringApplicationRunListeners listeners,
  4. ApplicationArguments applicationArguments) {
  5. // Create and configure the environment
  6. //创建ConfigurableEnvironment实例
  7. ConfigurableEnvironment environment = getOrCreateEnvironment();
  8. //commandline args 绑定到ConfigurableEnvironment中
  9. configureEnvironment(environment, applicationArguments.getSourceArgs());
  10. //发布ConfigurableEnvironment准备完毕事件
  11. listeners.environmentPrepared(environment);
  12. //绑定ConfigurableEnvironment到当前的SpringApplication实例中
  13. bindToSpringApplication(environment);
  14. ......
  15. //绑定ConfigurationPropertySourcesPropertySource到ConfigurableEnvironment中,name为configurationProperties,实例是SpringConfigurationPropertySources,属性实际是ConfigurableEnvironment中的MutablePropertySources
  16. ConfigurationPropertySources.attach(environment);
  17. return environment;
  18. }
  19. }

针对命令行参数的读取 和 application配置文件的读取我们需要关注

  • configureEnvironment(environment, applicationArguments.getSourceArgs());
    commandline args 绑定到ConfigurableEnvironment中
  • listeners.environmentPrepared(environment);
    发布ConfigurableEnvironment准备完毕事件,该事件会触发 classpath 下 application.properties 文件读取事件。

在这里可以得出结论: commandline args 参数的设置比 classpath application.properties 参数的读取早,所以优先级高。
【当然,按照读取顺序来评判优先级不完全准确,配置的优先级取决于在 List 中的位置,而 List 的可以进行随意位置的操作】

查看配置文件加载代码具体实现

下面来看看,两种配置文件加载的具体代码实现。

通过 SpringApplication#configureEnvironment 查看 commandline args 的配置读取

12.png
SpringApplication#configurePropertySources 关键代码

  1. public class SpringApplication {
  2. protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
  3. MutablePropertySources sources = environment.getPropertySources();
  4. ......
  5. if (this.addCommandLineProperties && args.length > 0) {
  6. // name = commandLineArgs
  7. String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
  8. if (sources.contains(name)) {
  9. PropertySource<?> source = sources.get(name);
  10. CompositePropertySource composite = new CompositePropertySource(name);
  11. composite.addPropertySource(new SimpleCommandLinePropertySource(
  12. "springApplicationCommandLineArgs", args));
  13. composite.addPropertySource(source);
  14. sources.replace(name, composite);
  15. }
  16. else {
  17. sources.addFirst(new SimpleCommandLinePropertySource(args));
  18. }
  19. }
  20. }
  21. }

代码逻辑很直观:

如果 PropertySources 列表中存在 命令行参数对应的 PropertySource 对象,那么替换掉,如果不存在,就新增一个 PropertySource 对象。

通过 listeners.environmentPrepared 查看 classpath 下的 application.properteis 文件的读取

13.png
② EventPublishingRunListener#environmentPrepared代码

  1. public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
  2. private final SimpleApplicationEventMulticaster initialMulticaster;
  3. @Override
  4. public void environmentPrepared(ConfigurableEnvironment environment) {
  5. this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
  6. this.application, this.args, environment));
  7. }
  8. }

代码逻辑:发布:ApplicationEnvironmentPreparedEvent 事件

来具体看看 ③ SimpleApplicationEventMulticaster#multicastEvent 代码

  1. public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
  2. // 任务线程池
  3. private Executor taskExecutor;
  4. public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
  5. for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
  6. Executor executor = getTaskExecutor();
  7. // 如果存在线程池,异步的方式执行
  8. if (executor != null) {
  9. executor.execute(() -> invokeListener(listener, event));
  10. }
  11. else {
  12. invokeListener(listener, event);
  13. }
  14. }
  15. }
  16. protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
  17. ......
  18. doInvokeListener(listener, event);
  19. ......
  20. }
  21. private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
  22. ......
  23. listener.onApplicationEvent(event);
  24. ......
  25. }
  26. }

代码逻辑:循环调用 ApplicationListener#onApplicationEvent 方法

classpath 下的 application.properties 配置的读取是在 ConfigFileApplicationListener 中完成的,而该监听器监听的是 ApplicationEnvironmentPreparedEvent 事件。

小贴士:默认从 classpath:/,classpath:/config/,file:./,file:./config/ 这几个目录中,查找默认名称为 application 的配置文件

来具体看看 ConfigFileApplicationListener 代码实现
14.png
聚焦 ⑥ ConfigFileApplicationListener.Loader.addLoadedPropertySources 代码

  1. public class ConfigFileApplicationListener
  2. implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
  3. private class Loader {
  4. private void addLoadedPropertySources() {
  5. // 从 environment 中获取 MutablePropertySources
  6. MutablePropertySources destination = this.environment.getPropertySources();
  7. // 这里为:OriginTrackedMapPropertySource
  8. List<MutablePropertySources> loaded = new ArrayList<>(this.loaded.values());
  9. Collections.reverse(loaded);
  10. String lastAdded = null;
  11. Set<String> added = new HashSet<>();
  12. for (MutablePropertySources sources : loaded) {
  13. for (PropertySource<?> source : sources) {
  14. if (added.add(source.getName())) {
  15. addLoadedPropertySource(destination, lastAdded, source);
  16. lastAdded = source.getName();
  17. }
  18. }
  19. }
  20. }
  21. private void addLoadedPropertySource(MutablePropertySources destination,String lastAdded, PropertySource<?> source) {
  22. ......
  23. // 将 application 读取到的 配置放入当前 MutablePropertySources 列表的末尾
  24. destination.addLast(source);
  25. ......
  26. }
  27. }
  28. }

代码主要逻辑:

将配置文件读取到的配置以 key-value 的形式抽象为 OriginTrackedMapPropertySource 对象,并放入到 Environment 中 propertySources 列表的最后一个位置。

总结

commandlineargs 配置加载之后构建的 PropertySourdce 对象放入List 列表的位置比 classpath 抽象的 PropertySource 靠前,所以 commandlineargs 配置的优先级比 classpath 下的 application.properties 配置 高

五、分布式配置中心的配置优先级

通过 上面针对 Spring Boot 外部化配置的分析,能够知道不同的配置文件最终以 PropertySource 对象的形式存在,并组成一个 List 列表存放在 Environment 抽象中。而配置文件的获取是以遍历 List 列表的方式获取的,也就是说不同配置文件的相同配置,程序究竟以那个为主取决于配置文件的抽象 PropertySource 在 List 列表的位置。

以 Spring Cloud Config 配置中心为例(携程的 Apollo 和 Spring Cloud Alibaba Nacos 的实现原理都一样),来看看 Spring Config Cloud 配置文件加载后在 propertySources List 列表中的占位:
04.png
通过截图能够看到,配置中心配置映射对象排在了 application 之前,甚至 commandlineargs 之前,所以配置读取优先级高。

简单分析,分布式配置的读取优先级为什么高于普通的 application.properties 读取

还是以 SpringApplication#run为入口进行源码分析

从代码执行前后来看:

  1. public ConfigurableApplicationContext run(String... args) {
  2. // 加载 application.propertes 配置
  3. ConfigurableEnvironment environment = prepareEnvironment(listeners,applicationArguments);
  4. // 触发加载分布式配置
  5. prepareContext(context, environment, listeners, applicationArguments,printedBanner);
  6. }

通过代码的执行逻辑来看,分布式配置的加载代码执行晚于 application.properteis 的加载。那分布式配置在程序中优先级反而高于 application.properties 配置?

其实,所有配置文件都是以 PropertySource 的形式存在并组成一个 List 列表。而配置的读取,就是遍历 List 列表,同样的配置,谁在 List 列表中越靠前,谁的优先级就高,那么不就简单了,就那啥,走走后门不就好了,你说你先来,不好意思,我直接走后门插队到第一个。

来看看Spring Boot 应用 PropertySource List 列表默认实现 MutablePropertySources

结构信息,如下:
05.png
关注 其中几个方法

  • MutablePropertySources#addFirst
    将某个配置放在整个当前队列中的第一个位置

    1. public class MutablePropertySources implements PropertySources {
    2. /**
    3. * Add the given property source object with highest precedence.
    4. */
    5. public void addFirst(PropertySource<?> propertySource) {
    6. removeIfPresent(propertySource);
    7. this.propertySourceList.add(0, propertySource);
    8. }
    9. }
  • MutablePropertySources#addLast
    将某个配置放置在当前整个队列的末尾。

    1. public class MutablePropertySources implements PropertySources {
    2. /**
    3. * Add the given property source object with lowest precedence.
    4. */
    5. public void addLast(PropertySource<?> propertySource) {
    6. removeIfPresent(propertySource);
    7. this.propertySourceList.add(propertySource);
    8. }
    9. }

除了上面两个方法,MutablePropertySources 还提供了 MutablePropertySources#addBefore(将配置放置在某个属性前面)以及 MutablePropertySources#addAfter (将配置放在某个属性前面)等多种方法实现。

到这里基本上能够理解为什么分布式配置中心的配置等级高于大多数的配置文件(如:命令行配置,classpath 下的 application 配置等)。等到大部分配置信息都加在完毕之后,分布式配置,再走走后门使用 MutablePropertySources#addFirst 插一下队就 O了。

了解了分布式配置的优先级问题,来看看 spring cloud config 加载配置代码具体实现

源码追踪 UML 时序如下:

PropertySourceBootstrapConfiguration#initialize -> PropertySourceBootstrapConfiguration#insertPropertySources:

PropertySourceBootstrapConfiguration#insertPropertySources 关键代码:

  1. public class PropertySourceBootstrapConfiguration implements
  2. ApplicationContextInitializer<ConfigurableApplicationContext>, Ordered {
  3. /**
  4. * @param propertySources 当前 Environment 上下文所有的配置列表
  5. * @param composite 从分布式配置中心获取到的配置信息,封装成 CompositePropertySource
  6. **/
  7. private void insertPropertySources(MutablePropertySources propertySources,
  8. CompositePropertySource composite) {
  9. ......
  10. propertySources.addFirst(composite);
  11. return;
  12. ......
  13. }
  14. }

代码主要逻辑:调用 MutablePropertySources#addFirst 将 分布式配置中心加载的配置放在列表第一位,而此时 application.properties 等配置信息已经被加载,所以分布式配置的在 list 列表中的位置比 application.properties 的配置高。

通过 debug 的方式验证

代码执行到 PropertySourceBootstrapConfiguration#insertPropertySources 时,此时 propertySources中已经存在的 PropertySource 对象列表信息如下:

06.png

代码执行到 PropertySourceBootstrapConfiguration#insertPropertySources 方法的最后一步 MutablePropertySources#addFirst 之后的 MutablePropertySources 中 PropertySource 对象列表的信息如下:

07.png
可以看到,此时 分布式配置中心加载的 配置列表位置比 application.properies 的位置高,设置比 命令行 commandline args 还高。

最后,再看看 当程序启动完毕之后,分布式配置中心配置,在列表中的最终位置:

08.png
可以看到,除了默认的 如 server.port 的属性,应用程序中的配置信息以分布式配置中心为主,只要分布式配置中心中配置有的属性都会以配置中心的配置数据为主。

六、总结

Spring Boot 应用中会存在很多种形式的配置文件,如:classpath 下的 application.properties ,java 启动命令时携带的命令行参数配置,Spring cloud 应用上下文对应的配置文件 bootstrap.properties ,分布式配置中的配置等。

所有配置文件在 Spring 应用中最终都会抽象成为 PropertySource 对象,这些对象会组成一个 List 列表,然后作为一个属性服务于 Environemnt 环境上下文。

而配置信息的读取,通过配置key(如:ext-info)获取对应的 value 值 。这个读取过程会遍历 PropertySource 组成的 List 列表,依次从各个配置文件映射的 PropertySource 中尝试读取 key=XX 的配置,一旦读取到就直接 return 返回。

而配置文件的优先级,就是配置文件映射到的 PropertySource 对象在 Environment 中的属性 propertysources List 列表中的位置,越靠前优先级越高。