【spring】 spring 环境与属性

从源码分析 spring 如何实现环境和属性配置

介绍

前言

在 spring 中,一个概念一般都会定义相关的接口,这个接口会定义一些使用这个概念的相关方法,然后会再定义一个 Configurable 接口用于定义配置相关方法。例如 PropertyResolver 接口定义了 getProperty 等方法,ConfigurablePropertyResolver 就定义了 setPlaceholderPrefix、 setPlaceholderSuffix 等配置相关的方法

环境和属性基础

属性源用于在装配 bean 时提供 bean 的属性。通过不同的 profile 配置,可以使得在不同的环境,应用会用不同的属性值去装配 bean。应用运行环境由 Environment 接口表示。Environment 有两个关键的概念:profile 和 property ,profile 表达运行环境,例如 dev, test 等;propert 提供各个属性值,由接口 PropertyResolver 定义相关方法。

相关概念

  • PropertySource 属性源
    包装属性源的名称和属性源

  • PropertySources
    多个 PropertySource 的集合。一个典型的实现是 MutablePropertySources, 用 CopyOnWriteArrayList 保存 PropertySource

  • PropertyResolver
    解析资源下的属性,主要方法有获取属性,以及将给定的 string 中的 place holder 替换

  • Environment
    保存 active profiles,继承 PropertyResolver

属性源的实现

spring 中由 MutablePropertySources 类实现 PropertySources 接口,MutablePropertySources 内部用一个 List 保存 PropertySource 对象。PropertySource 有多种不同的实现,例如 MapPropertySource 是一个在内部用 Map 保存属性的 PropertySource,RandomValuePropertySource 是一个每次从里面取值就会产生随机值的 PropertySource。

每一个 spring 应用都至少有一个 Environment 对象,而 Environment 对象有一个 PropertySources 成员。在 Environment 的默认实现 StandardEnvironment 中,会给 PropertySources 添加两个属性源:

  • systemProperties,PropertiesPropertySource 类型,保存所有通过 System.getProperties() 获取的属性
  • systemEnvironment,SystemEnvironmentPropertySource 类型,保存所有通过 System.getenv() 获取的属性

PropertiesPropertySource 同 SystemEnvironmentPropertySource 的区别是: 在 SystemEnvironmentPropertySource 的 key 中 . 和 _ 是等价的,并且不区分大小写

当需要用到属性值时,PropertyResolver 会由头到尾顺序地从 PropertySources 遍历所有 PropertySource,从中找到匹配的属性值。

spring boot 中的属性源

spring-boot 启动时,会向 environment 添加多个属性源,包括:

  • commandLineArgs,SimpleCommandLinePropertySource 类型,支持将命令行中 –key=value 形式的参数保存到 Map
  • configurationProperties,ConfigurationPropertySourcesPropertySource 类型,这个类型比较特殊,它是一个 Iterable<ConfigurationPropertySource> 的包装,在这里是 SpringConfigurationPropertySources 的包装,而 SpringConfigurationPropertySources 又包装了 environment 的 propertySources。
  • random,类型是 RandomValuePropertySource

在 ConfigFileApplicationListener 中, spring 根据 profiles 的顺序加载配置文件,并添加所有读到的配置文件到 propertySources。例如:

  • applicationConfig: [classpath:/application.yml],OriginTrackedMapPropertySource 类型,它将配置文件的属性读入到 Map 中保存
  • applicationConfig: [classpath:/application-test.yml],同上
  • applicationConfig: [classpath:/application-prod.yml],同上

完成环境初始化后的属性源顺序如下:

  1. configurationProperties
  2. commandLineArgs
  3. systemProperties
  4. systemEnvironment
  5. random
  6. applicationConfig: [classpath:/application-test.yml]
  7. applicationConfig: [classpath:/application-prod.yml] // prod 和 test 的顺序取决于 profiles 的设置,在后面配置的顺序会靠前
  8. applicationConfig: [classpath:/application.yml]

在 spring-boot 中,默认是通过 application.yml 进行配置,如果运行参数加上 –spring.profiles.active=test,spring 就会找 application-test.yml 这个文件的配置从而达到不同环境使用不同配置的效果。

spring cloud 配置中心

在使用 spring-cloud-config-client 时,spring 通过 PropertySourceBootstrapConfiguration (一个 ApplicationContextInitializer 接口的实现) 调用 ConfigServicePropertySourceLocator 的 locate 方法,后者将向远程服务器请求,并将返回的结果解析成 name 为 bootstrapProperties 的 CompositePropertySource,然后添加到 environment 的propertySources 中。在添加时,会根据远程的 overrideSystemProperties 和 allowOverride 配置将远程配置添加到 的propertySources 的不同位置,从而达到系统属性能否覆盖远程属性的效果。

在默认的情况下,属性源的顺序如下:

  1. bootstrapProperties
  2. configurationProperties
  3. systemProperties
  4. systemEnvironment
  5. random
  6. applicationConfig: [classpath:/application.yml]
  7. springCloudClientHostInfo
  8. applicationConfig: [classpath:/bootstrap.yml]
  9. defaultProperties

spring 优先使用远程配置

如果远程配置有 spring.cloud.config.overrideSystemProperties=false,则顺序如下:

  1. systemEnvironment
  2. bootstrapProperties

这样就优先使用 systemEnvironment,起到系统配置覆盖远程配置的效果

属性的使用 @Value 和 @ConfigurationProperties 的实现

@Value

在给 bean 注入属性时,会调用 AutowiredAnnotationBeanPostProcessor 的 postProcessProperties 方法,它枚举 bean 的所有需要注入的属性,然后用 StandardBeanExpressionResolver 解释并计算 @Value 中的表达式。在这个过程中,会用到 PropertySourcesPropertyResolver 来解析属性值,PropertySourcesPropertyResolver 会按顺序在 propertySources 中找到对应的属性值

@ConfigurationProperties

@ConfigurationProperties 注解需要配合 @EnableConfigurationProperties 注解一起使用。

@ConfigurationProperties 标记一个类,表示这个类的所有成员变量都要注入属性值

@EnableConfigurationProperties 注解在 Configuration 类上,用于开启 ConfigurationProperties 的功能。它通过 EnableConfigurationPropertiesImportSelector 注册一个 bean,类型是 EnableConfigurationProperties 中指定的类,然后 bean 在容器中初始化时,通过 ConfigurationPropertiesBinder 将所有属性值注入到这个 bean