springboot中@ConfigurationProperties注解的作用

版权声明:本文为博主原创文章,未经博主允许不得转载 https://blog.csdn.net/gs_albb/article/details/85019466

@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

参考文章

猜你喜欢

转载自blog.csdn.net/gs_albb/article/details/85019466
今日推荐