spring cloud外部化配置优先级

认识spring 上下文的层次性

引入spring boot actuator,开启所有endpoint,可以查看程序内部运行时的beans

什么是上下文的层次性?spring在启动时,可能需要启动多个上下文,可能某一个上下文是另一个上下文的parent。在程序初始化上下文时,parent级别的上下文优先于children初始化,parent初始化的bean能够在children中使用。这种父子的优先级与继承关系可以看作层次性。

在spring clound中,新增了一个独立的上下文bootstrap,用于提前加载某些资源。可以引入spring-cloud-starter依赖,启动一个简单的程序,利用actuator查看上下文信息。

{
"contexts": {
"application-1": {
"beans": {},
"parentId": "bootstrap"
},
"bootstrap": {
"beans": {},
"parentId": null
}
}
}

可以看出,这里初始化了两个context,其中一个是bootstrap,另一个是application-1,关系就是。

application-1.parentId -> bootstrap

意味着两者之间具有层次性。

或者我们可以手动设置spring默认上下文的parent上下文,代码如下。

@EnableAutoConfiguration
public class BootstrapTestApplication2 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext annotationConfigApplicationContext = new AnnotationConfigApplicationContext();
        annotationConfigApplicationContext.setId("mycontext");
        annotationConfigApplicationContext.refresh();
        SpringApplicationBuilder builder = new SpringApplicationBuilder(BootstrapTestApplication2.class);
        builder.parent(annotationConfigApplicationContext).run(args);
    }
}

最终beans的内容展示如下。

{
"contexts": {
"application-1": {
"beans": {},
"parentId": "mycontext"
},
"mycontext": {
"beans": {},
"parentId": "bootstrap"
},
"bootstrap": {
"beans": {},
"parentId": null
}
}
}

可以看出,手动设置的上下文已经是application-1的parent,但是bootstrap依旧是最顶级的上下文,这是由于bootstrap中有必须优先加载的资源信息。

BeanFactory实际管理bean,parent的bean子context可以使用。

org.springframework.context.support.AbstractApplicationContext#setParent 时, parent的environment的合并到子的environment。

org.springframework.cloud.bootstrap.BootstrapApplicationListener#onApplicationEvent监听ApplicationEnvironmentPreparedEvent事件,读取spring.cloud.bootstrap.enabled(只能命令行输入)属性判断是否需要进行Bootstrap上下文初始化。如果需要,就执行org.springframework.cloud.bootstrap.BootstrapApplicationListener#bootstrapServiceContext进行构建Bootstrap上下文。

上下文中的属性源管理(属性源优先级实现原理)?

属性源管理结构

我们都知道在spring boot中的属性外部化配置具有一定的顺序,最常见的就是命令行>application。需要注意的是在spring cloud中,对于配置在bootstrap和Application中的配置信息,spring cloud 组件涉及的配置一般都是使用bootStrap中的配置,因为spring cloud的Bootstrap context初始化时只加载Bootstrap的配置信息,此时还没加载Application.properties中配置信息 ,所以spring cloud实际使用Bootstrap而不会是Application,但是在程序中,读取相同的配置(@Value),取得值最终一定是较后加载的配置文件源配置的属性,也就是会取道Application。那么这种顺序是如何实现的,或者说为何属性配置读取时会有一种被覆盖感觉?

ApplicationContext Environment MutableProp List#PropertySource hold hold hold ApplicationContext Environment MutableProp List#PropertySource

一个ApplicationContext的属性配置依赖如上图所示,最终都是依赖到MutablePropertySources#propertySourceList里面,这里的PropertySource列表具有顺序,接下来会看一下是如何利用这个顺序实现配置优先级。

优先级实现原理

以命令行参数为例,跟踪一个springApplication的启动调用链如下。

  • org.springframework.boot.SpringApplication#run(java.lang.String…)
    • org.springframework.boot.SpringApplication#prepareEnvironment
      • org.springframework.boot.SpringApplication#configureEnvironment
        • org.springframework.boot.SpringApplication#configurePropertySources
protected void configurePropertySources(ConfigurableEnvironment environment,
      String[] args) {
    //获取Environment的属性源列表
   MutablePropertySources sources = environment.getPropertySources();
   if (this.defaultProperties != null && !this.defaultProperties.isEmpty()) {
      sources.addLast(
            new MapPropertySource("defaultProperties", this.defaultProperties));
   }
   if (this.addCommandLineProperties && args.length > 0) {
      String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
      if (sources.contains(name)) {
         PropertySource<?> source = sources.get(name);
         CompositePropertySource composite = new CompositePropertySource(name);
         composite.addPropertySource(new SimpleCommandLinePropertySource(
               "springApplicationCommandLineArgs", args));
         composite.addPropertySource(source);
          //如果已经有命令行属性源,就替换这个
         sources.replace(name, composite);
      }
      else {
          //没有命令行属性源的话就加到第一个中
         sources.addFirst(new SimpleCommandLinePropertySource(args));
      }
   }
}

sources.addFirst的操作就是保证属性读取优先级的关键,可以推测,属性读取是优先读取前面的,如果读取到就获取最前面的值。尝试跟踪读取Environment的属性的代码调用链,如下所示。

  • org.springframework.core.env.AbstractEnvironment#getProperty(java.lang.String)
    • org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String)
      • org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class, boolean)
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
   if (this.propertySources != null) {
      for (PropertySource<?> propertySource : this.propertySources) {
         if (logger.isTraceEnabled()) {
            logger.trace("Searching for key '" + key + "' in PropertySource '" +
                  propertySource.getName() + "'");
         }
         Object value = propertySource.getProperty(key);
         if (value != null) {
            if (resolveNestedPlaceholders && value instanceof String) {
               value = resolveNestedPlaceholders((String) value);
            }
            logKeyFound(key, propertySource, value);
            return convertValueIfNecessary(value, targetValueType);
         }
      }
   }
   if (logger.isDebugEnabled()) {
      logger.debug("Could not find key '" + key + "' in any property source");
   }
   return null;
}

上面 的代码很明显按照propertySources的顺序遍历属性源,查找属性,越前面的属性源优先级越高,因此保证读取时,在前面的属性源属性优先级高。

actuator的env可以查看所有propertysource,优先级为从上到下的顺序。

补上官方声明的spring配置优先级顺序

Spring Boot uses a very particular PropertySource order that is designed to allow sensible overriding of values. Properties are considered in the following order:

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).(jar外的配置文件-指明dev\test\product)
13.Profile-specific application properties packaged inside your jar (application-{profile}.properties and YAML variants).(jar内部配置文件-指明dev\test\product)
14.Application properties outside of your packaged jar (application.properties and YAML variants).(jar外配置文件)
15.Application properties packaged inside your jar (application.properties and YAML variants).(jar内配置文件)
16.@PropertySource annotations on your @Configuration classes.
17.Default properties (specified by setting SpringApplication.setDefaultProperties).



你可能感兴趣的:(spring,cloud)