@ConfigurationProperties是springboot新加入的注解,主要用于配置文件中的指定键值对映射到一个java实体类上。那么它是怎么发挥作用的呢?下面我们将揭开@ConfigurationProperties的魔法。
版本:springboot-2.0.6.RELEASE
1 概述
ConfigurationPropertiesBindingPostProcessor
这个bean后置处理器,就是来处理bean属性的绑定的,这个bean后置处理器后文将称之为properties后置处理器。你需要知道以下几件事:
- ioc容器context的
enviroment.propertySources
记录着系统属性、应用属性以及springboot的默认配置文件application.properties
中的配置属性等。properties后置处理器就是从其中找到匹配的配置项绑定到bean的属性上去的。 - 属性绑定是有覆盖性的,操作系统环境变量可以覆盖配置文件application.properties, java系统属性可以覆盖操作系统环境变量。更多的可以参考官网 https://docs.spring.io/spring-boot/docs/2.1.1.RELEASE/reference/htmlsingle/#boot-features-external-config
2 解析流程
2.1 属性资源有序性
上面提到过属性资源具有优先级,优先级高的会覆盖优先级低的。这里主要涉及到MutablePropertySources
这个类,这个类的层级关系如下
从类名上可以看出,这是一个可迭代查询的多变的属性资源容器,就像一个动态可扩容的容器list一样。正如javadoc所描述的那样,这个类提供了addFirst
,addLast
等方法,为PropertyResolver
进行有序的搜索属性资源提供了帮助。该类有一个非常重要的成员变量propertySourceList
,如下
public class MutablePropertySources implements PropertySources {
...
private final List<PropertySource<?>> propertySourceList = new CopyOnWriteArrayList<>();
...
}
在springboot的启动过程中,这个propertySourceList
会按照规则增加元素,优先级越高的属性资源在list容器中的索引值越小,位置越靠前。最终的一个可能结果如下:
systemProperties > systemEnvironment > random > applicationConfig
springboot解析属性资源绑定到bean上,就是按照这个优先级顺序的。
2.2 解析基本类型属性
如文末参考文章描述的那样,如果现在有一个类People
,只有一个基本属性name
,那么配置文件中的值是如何绑定的。
public class People {
private String name;
//getter, setter方法略
}
最终会调用到Binder类的findProperty方法,如下
private ConfigurationProperty findProperty(ConfigurationPropertyName name,
Context context) {
if (name.isEmpty()) {
return null;
}
//遍历属性资源文件,按照上文提到的属性资源顺序,直到根据name参数找到第一个不为空的属性
//才返回。
return context.streamSources()
.map((source) -> source.getConfigurationProperty(name))
.filter(Objects::nonNull).findFirst().orElse(null);
}
context.streamSource()
返回一个流式对象Stream<ConfigurationPropertySource>
, ConfigurationPropertySource是属性资源的描述接口,提供了通过属性名称获取特定属性的接口方法。
我们接着看context.stream
方法做了什么?
public Stream<ConfigurationPropertySource> streamSources() {
if (this.sourcePushCount > 0) {
return this.source.stream();
}
return StreamSupport.stream(Binder.this.sources.spliterator(), false);
}
springboot启动时,Binder.this.sources
实际上就是SpringConfigurationPropertySources
类。这个类有一个成员变量sources,存储着springboot启动过程中采集到的属性资源,就是2.1节讲到的MutablePropertySources。
/** 子类 MutablePropertySources**/
private final Iterable<PropertySource<?>> sources
lamda表达式真正流式遍历执行的时候,会调用到SpringConfigurationPropertySources$SourcesIterator的重写hasNext方法,而hasNext
方法最终会调用到SpringConfigurationPropertySources这个类的adapt
方法,这是一个适配器方法,它将PropertySource属性资源转化为ConfigurationPropertySource。
这样才能继续执行流式lamda表达式中的map方法,map((source) -> source.getConfigurationProperty(name))
我们先看一下这个接口的一些主要实现类:
这里着重关注一下SpringIterableConfigurationPropertySource
类,看一下它的getConfigurationProperty(name)
方法
@Override
public ConfigurationProperty getConfigurationProperty(
ConfigurationPropertyName name) {
// 调用父亲的方法
ConfigurationProperty configurationProperty = super.getConfigurationProperty(
name);
if (configurationProperty == null) {
// 方法是在父类实现的
configurationProperty = find(getPropertyMappings(getCache()), name);
}
return configurationProperty;
}
由lamda表达式的findFirst()可知,如果第一次在systemProperties
属性资源中找不到name对应的属性,会再次遍历,还会进入hasNext方法,debug的时候发现最终的属性资源列表的存储模型是CopyOnWriteArrayList$COWIterator
,它内部有一个指针cursor,记录着处理过的资源的位置,所以再次遍历是,不会从之前遍历过的属性资源中再去找name对应的属性。
个人感觉SpringConfigurationPropertySources这个类内容很丰富,对属性资源的优先级处理就在这个类的内部私有静态类SourcesIterator上,这里也依托了2.1章节提到的MutablePropertySources.propertySourceList
保存的属性资源的有序性。
2.3 解析List
参考文章
- spring boot 源码解析13-@ConfigurationProperties是如何生效的 https://blog.csdn.net/qq_26000415/article/details/78942494