前言
@ConfigurationProperties
注解是 SpringBoot 提供的一种更加便捷来处理配置文件中的属性值的方式,可以通过自动绑定和类型转换等机制,将指定前缀的属性集合自动绑定到一个Bean对象上。
加载原理
在 Springboot 启动流程加载配置的 prepareEnvironment()
方法中,有一个重要的步骤方法 bindToSpringApplication(environment)
,它的作用是将配置文件中的属性值绑定到被 @ConfigurationProperties
注解标记的 Bean对象中。但此时这些对象还没有被 Spring 容器管理,因此无法完成属性的自动注入。那么这些Bean对象又是什么时候被注册到 Spring 容器中的呢?
这就涉及到了 ConfigurationPropertiesBindingPostProcessor
类,它是 Bean后置处理器,负责扫描容器中所有被 @ConfigurationProperties 注解所标记的 Bean对象。如果找到了,则会使用 Binder 组件将外部属性的值绑定到它们身上,从而实现自动注入。
bindToSpringApplication
主要是将属性值绑定到 Bean 对象中;ConfigurationPropertiesBindingPostProcessor
负责在 Spring 容器启动时将被注解标记的 Bean 对象注册到容器中,并完成后续的属性注入操作;
从springboot程序启动看配置初始化
Springboot 程序启动加载流程里,会执行SpringApplication.run
中的prepareEnvironment()
方法进行配置的初始化,那初始化过程每一步都做了什么呢?
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
/**
* 1、创建 ConfigurableEnvironment 对象:首先调用 getOrCreateEnvironment() 方法获取或创建
* ConfigurableEnvironment 对象,该对象用于存储环境参数。如果已经存在 ConfigurableEnvironment 对象,则直接使用它;否则,根据用户的配置和默认配置创建一个新的。
*/
ConfigurableEnvironment environment = getOrCreateEnvironment();
/**
* 2、解析并加载用户指定的配置文件,将其作为 PropertySource 添加到环境对象中。该方法默认会解析 application.properties 和 application.yml 文件,并将其添加到 ConfigurableEnvironment 对象中。
* PropertySource 或 PropertySourcesPlaceholderConfigurer 加载应用程序的定制化配置。
*/
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 3、加载所有的系统属性,并将它们添加到 ConfigurableEnvironment 对象中
ConfigurationPropertySources.attach(environment);
// 4、通知监听器环境参数已经准备就绪
listeners.environmentPrepared(bootstrapContext, environment);
/**
* 5、将默认的属性源中的所有属性值移到环境对象的队列末尾,
这样用户自定义的属性值就可以覆盖默认的属性值。这是为了避免用户无意中覆盖了 Spring Boot 所提供的默认属性。
*/
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
// 6、将 Spring Boot 应用程序的属性绑定到环境对象上,以便能够正确地读取和使用这些配置属性
bindToSpringApplication(environment);
// 7、如果没有自定义的环境类型,则使用 EnvironmentConverter 类型将环境对象转换为标准的环境类型,并添加到 ConfigurableEnvironment 对象中。
if (!this.isCustomEnvironment) {
EnvironmentConverter environmentConverter = new EnvironmentConverter(getClassLoader());
environment = environmentConverter.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
// 8、再次加载系统配置,以防止被其他配置覆盖
ConfigurationPropertySources.attach(environment);
return environment;
}
看看它的配置加载流程步骤:
- 创建 环境对象
ConfigurableEnvironment
用于存储环境参数; configureEnvironment
方法加载默认的application.properties
和application.yml
配置文件;以及用户指定的配置文件,将其封装为 PropertySource 添加到环境对象中;attach()
: 加载所有的系统属性,并将它们添加到环境对象中;listeners.environmentPrepared()
: 发送环境参数配置已经准备就绪的监听通知;moveToEnd()
: 将 系统默认 的属性源中的所有属性值移到环境对象的队列末尾,这样用户自定义的属性值就可以覆盖默认的属性值。bindToSpringApplication
: 应用程序的属性绑定到 Bean 对象上;attach()
: 再次加载系统配置,以防止被其他配置覆盖;
上边的配置加载流程中,各种配置属性会封装成一个个抽象的数据结构 PropertySource
中,这个数据结构代码格式如下,key-value形式。
public abstract class PropertySource<T> {
protected final String name; // 属性源名称
protected final T source; // 属性源值(一个泛型,比如Map,Property)
public String getName(); // 获取属性源的名字
public T getSource(); // 获取属性源值
public boolean containsProperty(String name); //是否包含某个属性
public abstract Object getProperty(String name); //得到属性名对应的属性值
}
PropertySource
有诸多的实现类用于管理应用程序的配置属性。不同的 PropertySource 实现类可以从不同的来源获取配置属性,例如文件、环境变量、命令行参数等。其中涉及到的一些实现类有:
关系图
MapPropertySource
: Map 键值对的对象转换为 PropertySource 对象的适配器;PropertiesPropertySource
: Properties 对象中的所有配置属性转换为 Spring 环境中的属性值;ResourcePropertySource
: 从文件系统或者 classpath 中加载配置属性,封装成 PropertySource对象;ServletConfigPropertySource
: Servlet 配置中读取配置属性,封装成 PropertySource 对象;ServletContextPropertySource
: Servlet 上下文中读取配置属性,封装成 PropertySource 对象;StubPropertySource
: 是个空的实现类,它的作用仅仅是给 CompositePropertySource 类作为默认的父级属性源,以避免空指针异常;CompositePropertySource
: 是个复合型的实现类,内部维护了 PropertySource集合队列,可以将多个 PropertySource 对象合并;SystemEnvironmentPropertySource
: 操作系统环境变量中读取配置属性,封装成 PropertySource 对象;
上边各类配置初始化生成的 PropertySource 对象会被维护到集合队列中。
List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>()
配置初始化完毕,应用程序上下文AbstractApplicationContext
会加载配置,这样程序在运行时就可以随时获取配置信息了。
private void prepareContext(DefaultBootstrapContext bootstrapContext, ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 应用上下文加载环境对象
context.setEnvironment(environment);
postProcessApplicationContext(context);
.........
}
3、读取配置
看明白上边配置加载的流程,其实读取配置就容易理解了,无非就是遍历队列里的PropertySource
,拿属性名称name
匹配对应的属性值source
。PropertyResolver
是获取配置的关键类,其内部提供了操作PropertySource
队列的方法,核心方法getProperty(key)
获取配置值,看了下这个类的依赖关系,发现 Environment
是它子类。
那么直接用 PropertyResolver 来获取配置属性其实也是可以的,到这我们就大致明白了 Springboot 配置的加载和读取了。
@Slf4j
@SpringBootTest
public class EnvironmentTest {
@Resource
private PropertyResolver env;
@Test
public void var1Test() {
String var1 = env.getProperty("env101.var1");
log.info("Environment 配置获取 {}", var1);
}
}