深入解析spring boot配置加载原理,配置文件的加载顺序是怎么实现的?
前言
相信很多人面试都被问到过spring boot配置的加载的顺序是怎么样的,他是怎么一个实现方式,我也被问到过,接下来这篇文章将详细解析一下spring boot配置的加载流程到底是怎么实现的。
在开始前我准备几个问题
- 简单的spring boot项目里面能不能加载bootstrap依赖?
- 配置文件的加载顺序是怎么实现的?
准备工作
-
新建spring boot项目
-
添加配置文件(application.properties)
#application.properties name=application-properties -
添加controller
@RestController @RequestMapping public class TestController { @Value("${name}") private String name; @RequestMapping("/getValue") public String getValue(){ return "name:"+name; } }
深入源码解析
1. 寻找加载配置的入口方法
我们从springboot的入口方法(run)一直点进去可以看到一个164行有个叫this.prepareEnvironment的方法,这里是环境配置准备的入口
我们在这个方法的下方debugger住,我们可以看到ConfigurableEnvironment已经加载好了,在框住的红色区域内正是我们配置文件的name和对应的属性
在这里我们记住几个重要的关键信息
- environment
- propertySources
- propertySourcesList
很明显,我们的配置是加载在propertySourcesList里面的

2. spring boot中的监听器
我们继续进入prepareEnvironment方法,可以看到一个叫listeners.environmentPrepared的方法。

顾名思义,好像是使用监听器加载环境配置?
我们再进入到environmentPrepared去看看

可以发现,监听器们会在此处调用执行方法,doWithListeners会在此处执行传入进来的监听器,spring.boot.application.environment-prepared表示环境准备的监听器
我们继续往下看看,进入listener.environmentPrepared方法
这里建议大家看着源码一步一步跟着追踪,不然可能会比较难懂
- 点进后看到
SpringApplicationRunListener.environmentPrepared SpringApplicationRunListener是个接口,往下看到他的实现方法EventPublishingRunListener- 继续往下
initialMulticaster.multicastEvent - 可以看到
invokeListener和doInvokeListener,这里就是执行监听器里面的监听事件 - 在
doInvokeListener中可以看到listener.onApplicationEvent(event) - 进去方法里可以看到,
ApplicationListener是个接口,有个叫onApplicationEvent的方法,我们找到他的实现类EnvironmentPostProcessorApplicationListener,这个是环境处理监听器,里面有个this.onApplicationEnvironmentPreparedEvent的方法 - 接下来重点来了,我们可以看到下面这块代码
while(var4.hasNext()) {
//获取环境处理器
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
//执行所有实现了环境处理器的接口
postProcessor.postProcessEnvironment(environment, application);
}

2.1 EnvironmentPostProcessor接口
我们可以看到,在EnvironmentPostProcessorApplicationListener中循环调用了EnvironmentPostProcessor的postProcessEnvironment方法
而spring boot在spring.factories提供了一个提供了一个spi,把所有EnvironmentPostProcessor的实现类通过spring.factories的方式加载到spring boot中,如下图所示
ps:spring.factories是一个spring boot的重点知识,这里不会重点展开

这里我们留下一个问题:既然这里提供了一个spi机制,我们是不是也可以通过这个机制实现一个Environment处理器呢
3.如何找到application命名的配置文件的?
我们继续看到 postProcessor.postProcessEnvironment(environment, application)方法,找到其中一个EnvironmentPostProcessor的实现类,叫做ConfigDataEnvironmentPostProcessor,找到他的实现方法postProcessEnvironment

这里有两个重点,一个是getConfigDataEnvironment,另一个是processAndApply
getConfigDataEnvironment用来加载解析起,比如properties或者yml,yaml那些processAndApply用来解析数据并且把数据设置进去environment里面
3.1 解析getConfigDataEnvironment方法,为什么bootstrap配置会失效?
我们先来看getConfigDataEnvironment方法,点进去看本质是new了一个ConfigDataEnvironment,我们从他的构造方法可以看到有个叫做this.createConfigDataLocationResolvers方法,解析器就是在这加载的。

可能到这里还是有点蒙,这个解析器到底是个啥,我们再点进去看看。作为一个码农,一定要刨根到底~
点进去发现还是一样,new了一个ConfigDataLocationResolvers

这里有又有两个重点
-
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, resourceLoader.getClassLoader())
通过
spring.factories获取两个解析器,重点是StandardConfigDataLocationResolver为标准化解析器
-
instantiator.instantiate(resourceLoader.getClassLoader(), names)
实例化两个解析器,
ConfigTreeConfigDataLocationResolver这里细讲,我们来看StandardConfigDataLocationResolver
这里又细分3个小点
-
DEFAULT_CONFIG_NAMES我们可以看到。他默认是找到application,所以为什么我们的配置文件都以application命名的原因 -
再次通过
spring.factories获取数据源加载器PropertiesPropertySourceLoader和YamlPropertySourceLoader
看名字就知道,这两个加载分别加载了properties和ymal的文件
PropertiesPropertySourceLoader是用来解析properties和xml的
YamlPropertySourceLoader是用来解析yml和yaml的
所以在最后会和解析器中的application拼接成application.properties或者application.yml等
-
最终
configNames确定是application还是其他名称,没有外部定义的话默认为application,所以为什么很多人问在spring boot中配置文件以bootstrap命名会失效,因为在寻常spring boot项目中解析器压根就没有定义bootstrap的文件名,所以无法解析。而在spring cloud中会有一个监听器往binder里加了个bootstrap变量,binder.bind("spring.config.name", String[].class)。
-
3.2 processAndApply预加载配置
processAndApply字面意思:处理和应用
首先我们找到里面的processInitial中的contributors.withProcessedImports里面


这里有个while循环,会在红框的resolveAndLoad这里循环两次分别解析yml和properties,就是用到上面3.1所说的PropertiesPropertySourceLoader和YamlPropertySourceLoader

进去resolveAndLoad可以看到他调用了两个方法一个是resolve和load
- resolve,加载解析器,把解析器的属性拼接成
application.properties或者application.yml等 - load,加载配置文件的数据,但是此时还并没有加载进
propertySourcesList里。相当于先加载到内存,最终把所有流程处理完才加载到propertySourcesList里

3.3 将加载的配置放到propertySourcesList中
继续回看到processAndApply方法,最下面有个applyToEnvironment,我们点进去看看

可以看到,前面check方法都是用来检查配置的,我们主要看applyContributor方法
后面的方法根据关键字profiles,activeProfiles不难看出,是用来加载不同环境的配置,例如dev,test,prod等

可以看到配置文件最终是在这里添加到propertySourcesList里面的

4.什么时候加到@value注解里
这里浅谈一下,毕竟这章重点在上面已经讲完了
在run方法的refreshContext里,有个refresh就是在这加载的,如下图所示

然后通过反射生成一个AutowiredAnnotationBeanPostProcessor实例
我们可以看到他的构造方法里面添加了Autowired和Value两个注解

最后在AutowiredMethodElement内部类里将值自动装配进去

扩展
1. 实现自定义命名的配置文件
我们在上面的2.1说到,我们是不是也可以通过这个机制实现一个Environment处理器?当然是可以的
1.1 添加test.properties文件
#test.properties
name=test-propertis
注释application.properties里面的配置
1.2 添加TestEnvironmentPostProcessor类并实现EnvironmentPostProcessor接口
public class TestEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final Properties properties=new Properties();
private String propertiesFile="test.properties";
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
Resource resource=new ClassPathResource(propertiesFile);
PropertySource<?> propertySource = loadProperties(resource);
//添加到propertySourcesList中
environment.getPropertySources().addLast(propertySource);
}
private PropertySource<?> loadProperties(Resource resource){
if(!resource.exists()){
throw new RuntimeException("file not exist");
}
try {
//加载test.properties
properties.load(resource.getInputStream());
return new PropertiesPropertySource(resource.getFilename(),properties);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
1.3 添加spring.factories文件
新建META-INF文件夹,并添加spring.factories文件

org.springframework.boot.env.EnvironmentPostProcessor=\
com.nssnail.main.config.TestEnvironmentPostProcessor
1.4 调试
访问http://localhost:8080/getValue接口(代码在上面准备工作目录下)
结果显示访问成功,并没有报错

1.5 自定义yml
上面的配置只适用properties,那么像自定义yml配置怎么办呢,代码如下所示
public class TestEnvironmentPostProcessor implements EnvironmentPostProcessor {
private final YamlPropertySourceLoader loader = new YamlPropertySourceLoader();
@Override
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
String propertiesFile = "test.yml";
Resource resource=new ClassPathResource(propertiesFile);
PropertySource<?> propertySource = loadProperties(resource);
//添加到propertySourcesList中
environment.getPropertySources().addLast(propertySource);
}
private PropertySource<?> loadProperties(Resource resource){
if(!resource.exists()){
throw new RuntimeException("file not exist");
}
try {
return loader.load("test",resource).get(0);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2. 自定义配置文件的顺序
我们可以看到propertySourcesList有四个add方法,如下图所示

| 方法名 | 含义 |
|---|---|
| addFirst | 加在propertySourcesList的最前面 |
| addLast | 加在propertySourcesList的最后面 |
| addAfter | 加在propertySourcesList中某个配置的后面 |
| addBefore | 加在propertySourcesList中某个配置的前面 |
配置文件的加在顺序跟这个propertySourcesList的顺序有关,最前面的配置优先级越高,所以我们在上面的addLast是放在最后面,意为优先级最低
这是我们再次把application.properties的配置放开

test.properties保持不变

访问http://localhost:8080/getValue
很明显,返回的是application-properties

这时我们将TestEnvironmentPostProcessor中的addLast改为addFirst

再次访问http://localhost:8080/getValue
可以看到,返回的是test-properties,这验证了上面的观点,配置文件的优先级是跟propertySourcesList的顺序有关的

3. 实现其他spi接口?
既然我们可以通过实现EnvironmentPostProcessor接口来扩展自定义配置文件,他们我们是不是可以通过实现PropertySourceLoader或者ConfigDataLocationResolver来扩展自定义配置解析器呢?这里大家可以自己动手试下
总结
- 配置是放在
ConfigurableEnvironment的propertySourcesList中的 - 寻常spring boot项目默认是
无法加载bootstrap配置的,而在spring cloud项目中一般有配置外部的bootstrap变量,所以可以正常加载 - 自动装配在
AutowiredAnnotationBeanPostProcessor中实现 - 配置文件的加载顺序跟
propertySourcesList的顺序有关
ps:有不对的地方欢迎指出,转载注明出处,谢谢
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)