本文是基于SpringBoot2.4.0之前的版本分析的,2.4.0之后有所不同
想必大家对SpringBoot中的application.properties(或application.yaml)文件都是再熟悉不过的了。它是应用的配置文件,我们可以把需要的一些配置信息都写在这个文件里面,需要的时候,我们可以通过@Value注解来直接获取即可,那这个文件是什么时候以及如何被应该加载的呢?这一直是我之前疑惑的地方,也是终于有时间对这一部分进行了详细的源码分析。
整个配置文件的加载过程其实就是一个事件监听的机制来实现的,整个过程的主要流程如下:
在SpringBoot启动的时候,首先需要创建SpringApplication对象,而就在创建这个对象的时候,会读取spring.factories配置文件获取其中的所有配置的Application Listeners的全类名,并创建这些监听器的对象,存放到SpringApplication的属性listeners中,在这其中就有一个ConfigFileApplicationListener监听器(如下图),这个就是和读取配置文件相关的一个监听器;好了,当ConfigFileApplicationListener这个监听器创建好了之后,就开始监听它可以处理的两个事件了【ApplicationEnvironmentPreparedEvent/ApplicationPreparedEvent】。
当SpringApplication对象创建完成之后就会调用该对象的run方法,run方法中准备环境的方法prepareEnvironment中发布了一个ApplicationEnvironmentPreparedEvent事件。睁大眼睛看下,这个事件不就是前面创建的ConfigFileApplicationListener正在监听可以处理的事件吗?所以后面读取配置文件的逻辑就顺利成章的走了起来;这就叫什么:你需要什么,我刚好有,我给你呗。
如果我直接从源码一点一点的来分析,感觉好无聊啊,我觉得你们肯定也会这么想的。既然配置文件的使用我们已经熟能生巧了,那我们带着我们知道的知识点去来看源码那不是更好吗,这就叫:知其然更要知其所以然。
其实这个我们盲猜也知道,这肯定是通过不同的加载器来进行不同的后缀进行加载的。如果你能想到这就不错了,我们来一起看看源码里是怎么处理的:在底层构建加载的Loader类的时候,SpringBoot就从spring.factories的配置文件中读取到了两种属性资源加载器并进行了实例化(如下图),即:PropertiesPropertySourceLoader、YamlPropertySourceLoader;
那就接着看一看这两个属性资源加载器分别能处理哪个配置文件以及是如何发挥作用的(如下图):看代码分析,SpringBoot会在找好配置文件的名称以及profile后,通过遍历所有的属性资源加载器来拼接后缀,然后再去指定的路径下进行加载这些文件,没有的话就直接跳过continue。
我们使用SpringBoot官网自带的脚手架生成项目的时候,默认给我们生成的就:application.properties,这显然后面底层SpringBoot默认的配置文件名称就是application,我们看下底层的代码(如下):在获取配置文件的名字的时候,SpringBoot底层首先会在environment中查找spring.config.name这个配置,如果有的话,就会返回spring.config.name配置的信息作为配置文件的名字,而如果没有配置spring.config.name的话,就会使用默认的application作为配置文件的名称。如果我们需要指定特性的名字作为配置文件的名字,那我们只需要在启动之前配上spring.config.name即可。
我们知道在SpringBoot启动的时候是先加载application.properties,然后在加载我们配置的spring.profiles.active指定环境的配置文件,比如说:spring.profiles.active=dev,那么在加载完application.properties之后,就会继续加载application-dev.properties文件。之所以会优先加载application.properties是因为,在初始化profiles的双向队列时,SpringBoot首先放入了一个null,而在profile值为null的时候,是直接加载配置文件名称拼上后缀的,不进行profile拼接的,所以application.properties得到了优先加载的机会;后面再对配置的spring.profiles.active以及spring.profiles.inclue等文件拼接上profile后再进行加载;通过设置不同的profile(比如:dev,test.prod等)就可以实现激活不同环境了(源码如下)。当我们没有指定任何active的profile时,SpringBoot底层会默认的网profiles集合里面加入一个名为default的profile,这也是为什么当我们没有指定激活的配置文件的时候写一个application-default.properties时也是会被加载到的原因。
初始化设置,加载前的准备工作
这一点是可以参考 SpringBoot的官方文档,文档上写的很详细的:
官网中提到的一个注意点:
spring.config.name and spring.config.location are used very early to determine which files have to be loaded. They must be defined as an environment property (typically an OS environment variable, a system property, or a command-line argument)
这两个属性的设置是要很早设置的,因为为了能加载到指定的文件,路径我们必须在要开始加载这个文件之前就指定好啊是吧?所以推荐我们在操作系统的环境变量,系统属性或者启动项目的命令行中使用,方能生效
其实在我们调用load方法的时候,一直传入了一个函数式接口DocumentConsumer(如下代码),其中这个addToLoaded(MutablePropertySources::addLast, false)就是函数式接口的实现,这就是函数式变成,不得不说Spring的开发人员还是腻害的。
load(profile, this::getPositiveProfileFilter,addToLoaded(MutablePropertySources::addLast, false));
在我们加载完配置文件并封装成Document后,调用了上面DocumentConsumer的accept方法,将Document对象的PropertySources封装到了MutablePropertySources的propertySourceList中,并将MutablePropertySources对象放入了loaded集合中:
然后等到所有的配置文件都加载封装完成后,会统一将这些封装后的MutablePropertySources对象放入到Environment中。也就是说,最终我们所有的配置文件的信息都是加载到了Environment中的,以后我们要是拿都是在Environment中拿的
既然以后我们拿配置文件的信息都是在Environment中拿,那我们就可以探索@Value是如何在Environment的了…还有,这仅仅是SpringBoot情况下的文件加载,当引入SpringCloud后,有了bootstrap配置文件,就牵扯到了父子容器了,等有时间了,我们一起探讨