springboot加载application.properties配置文件过程

最近发现,带着问题去阅读源码会是一个比较折中的方式。这样你可以快速掌握一些核心的问题和逻辑,又不至于太陷入具体细节问题而浪费时间。如果希望具体的了解某些点的时候再把它翻出来详细阅读即可,这样当你解决完一个个问题的时候也就逐渐从外层到内层,从粗略到具体地了解它。

接下来进入本文正式内容

我们在使用springboot进行开发的时候经常会接触application.properties(application.yml也一样,这里简单起见)配置文件,我们可以关注几个主要问题点

提出问题

1)再springboot启动过程中,application.properties是什么时候被加载?

2)默认加载过程是怎么样的?

3)配置多环境指定profile的时候又是怎么加载的?

问题1:application.properties文件是什么时候被加载的?

首先,我们要知道springboot采用了事件监听的方式去加载application.properties配置文件。也就是说,在一开始程序启动的时候设置了一个EventListener,当某个事情做完以后publish一个对应的Event出去触发该监听器。

springboot在创建Environment对象以后,会去触发一个监听器EventPublishingRunListener,将发布一个ApplicationEnvironmentPreparedEvent,这个Event将触发ConfigFileApplicationListener。ConfigFileApplicationListener这个监听器类也就是加载application.properties核心的类。

下面我们从run方法开始,跟一下代码

getRunListeners(args)方法将会从spring.factories中获取SpringApplicationRunListener接口的实现类,如

这个时候EventPublishingRunListener这个监听器将会被加载,然后调用prepareEnvironment的时候顺带着被做实参传入,我们进入prepareEnvironment方法。

我们看到,prepareEnvironment方法会创建一个Environment实例对象并进行一些配置处理,而后就会触发EventPublishingRunListener监听器,执行其environmentPrepared方法。这里我们可以猜测到EventPublishingRunListener调度角色,会构造出对应的事件并分发给对应的监听器。

进入EventPublishingRunListener,我们看到该方法创建了一个ApplicationEnvironmentPreparedEvent事件,然后广播了出去。

 

那么这个新建的事件广播给哪些监听器呢?getApplicationListeners方法将会给出答案,我们断点就可以看到

我们看到了一开始说的ConfigFileApplicationListener监听器出现了,invokeListener方法将会触发该监听器的onApplicationEvent方法。我们再跟进ConfigFileApplicationListener的onApplicationEvent方法

继续跟进onApplicationEnvironmentPreparedEvent方法

我们看到loadPostProcessors方法去加载了Environment的后置处理器,并且调用postProcessEnvironment方法。ConfigFileApplicationListener也实现了EnvironmentPostProcessor接口,所以postProcessEnvironment方法再当前类中有实现。我们跟进当前类的postProcessEnvironment方法。

再跟进addPropertySources方法

addPropertySources方法将会实例化一个加载器Loader并开始加载配置。

到这里我们基本上知道第一个问题的答案,Springboot是在Environment创建以后发布一个事件触发ConfigFileApplicationListener监听器并开始加载application.properties配置的。

问题2:默认加载过程是怎么样的?

 上面,我们知道addPropertySources方法最终是实例化一个Loader并调用其load方法去加载配置文件,显然application.properties这个配置文件的加载过程也就再这里实现。

我们先看看Loader的构造方法做了什么

这个构造方法值得关注的一点是它从spring.factories中加载了PropertySourceLoader接口的实现类,该接口顾名思义就是用来加载配置文件资源的,接口的实现类如下

 

前者加载properties、xml扩展文件,后者加载yml、yaml扩展文件

回到addPropertySources方法

在Loader实例化完毕以后,就调用其load()方法,我们跟进load方法看看

很显然,load方法实现了加载文件的核心逻辑。我们先关注一下initiallizeProfiles()方法

我们注意到,一开始添加了一个null,这个是为了保证默认的application.properties会被优先处理。

而initializeProfiles方法希望从Environment里面拿到现有的profiles配置,不过显然默认是不会有的,所以size==1,将会拿到Environment中默认的profiles,而默认的profiles只会有一个default。

所以initializeProfiles方法最终将产生一个这样的数据

profiles = [null, "default"]

回到load方法,继续往下看

首先profile=null会被找到,由于为null,所以将会直接调用另一个load方法,我们跟进另一个load方法

这里我们看到getSearchLocations将会获取所有待搜索的目录位置,我们进入getSearchLocations方法

可以看到,Environment默认是没有配置的,所以最终会找到默认的常量值,如下:

到这里,我们通过getSearchLocations方法获取了默认会被搜索的文件夹路径,回到刚才的load方法

 

找到待搜索目录以后,将会遍历该目录,再通过getSearchNames()方法获取待搜索文件的名字,我们看看getSearchNames()方法

显然和getSearchLocations方法一样,最后会取到默认值,如

我们看到,被搜索的配置文件名默认是"application"。

到这里,我们拿到了待搜索的目录、待搜索的文件名,下面就是去目录下搜索对应的文件了,回到刚刚的load方法

跟进另一个load方法

我们已经获得了name=application,所以直接进入下面一段代码。

首先遍历了propertySourceLoaders,这个ProperySourceLoader也就是我们一开始Loader实例化的时候从spring.factories文件加载出来的两个加载器properties、yml文件加载器。

在遍历中,将会根据:目录 + 文件名 + 文件扩展名 去加载默认的配置文件,例如:classpath:/application.properties,我们跟进loadForFileExtension方法

我们一开始的profile=null,所以这里将直接调用另一个load方法,跟进看看,这个load方法代码较长,截取核心如下

首先loadDocuments方法将会使用load加载器加载配置文件为Document,然后document会被遍历分别处理。这里的consumer是从一开始的load方法传入的,我们回头看看第一个load,如

我们看到consumer是addToLoaded方法返回出来的,并且将MutablePropertySourcs的addLast作为方法应用传递进去,我们进入addToLoaded方法

我们看到,之前Loader加载出来的每个document的PropertySource将会被添加到loaded对象当中,这里我们稍微关注一下loaded对象是什么结构,如

loaded对象承担的责任是存储Profile和Properties的属性值的映射,所以我们第一个application.properties文件加载到loaded以后就变成 key=null,value=Application配置文件的属性值了。

这样,我们一个application.properties文件也就被加载出来了,还需要最后一步将它添加到Environment环境变量当中,我们回到最开始的load方法,如

addLoadedPropertySources方法将会把每个consumer添加到loaded对象当中的PropertySource给添加到Environment中,我们跟进addLoadedPropertySources方法

这里做了一个遍历,我们跟进addLoadedPropertySource方法

source被添加到了destination中,也就是Environment的成员对象MutablePropertySources当中。

到这里,第二个问题的解答就结束了,默认的加载过程也就是将application.properties或者application.yml通过加载器加载为PropertySource,并添加到Environment当中。

问题3:配置多环境指定profile的时候又是什么原理?

配置多环境切换的时候我们通常是这样做的

application.properties文件中指定那个启用,这里配置启用dev文件

那么,为什么application.properties指定了spring.profiles.active以后就会自动加载对应的配置文件呢?比如这里指定了dev,那么就会去加载application-dev.properties配置文件。

首先,我们是在application.properties文件中指定的,所以加载什么配置文件也肯定是在application.properties文件加载完以后决定的,我们进入刚才讨论第二个问题的时候loadDocuments的位置,如图

我们看到application.properties配置的profile会被拿出来,再进入addActiveProfiles看看添加到哪里去

直接被添加到了原先profiles=[null, "default"]的集合当中,并且最后还把"default"给移除掉了。这说明当加载完application.properties以后,相应的profiles将被重新指定,我们回到一开始的load方法中

load方法将找到配置的"dev",并且跟之前一样调用load方法。我们再一路跟进,直到loadForFileExtension方法

我们看到,当有profile存在的时候,文件名 = 前缀 + profile + 扩展名,例如 application-dev.properties,并加载该文件,获取PropertySource添加到loaded中,并最终加载到Environment当中。到这里,第三个问题就解答完毕了。

springboot加载配置文件还有很多小细节值得了解,比如中文乱码问题、加载顺序问题、重复配置覆盖问题等等~

猜你喜欢

转载自www.cnblogs.com/lay2017/p/11456556.html