SpringBoot是如何加载配置文件的?
前言
本文针对版本
2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚一些常见的问题比如:
- SpringBoot从哪里开始加载配置文件?
- SpringBoot从哪些地方加载配置文件?
- SpringBoot是如何支持
yaml
和properties
类型的配置文件? - 如果要支持
json
配置应该如何做? - SpringBoot的配置优先级是怎么样的?
- placeholder是如何被解析的?
带着我们的问题一起去看一下SpringBoot配置相关的源代码,找出问题的答案。
SpringBoot从哪里开始加载配置文件?
SpringBoot加载配置文件的入口是由
ApplicationEnvironmentPreparedEvent事件进入的,SpringBoot会在SpringApplication的构造函数中通过
spring.factories文件获取ApplicationListener的实例类:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { ... setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); ... }
spring.factories中有一个
ConfigFileApplicationListener类,它会监听
ApplicationEnvironmentPreparedEvent然后再加载配置文件 :
# Application Listeners org.springframework.context.ApplicationListener= org.springframework.boot.context.config.ConfigFileApplicationListener ...
有了事件和事件处理的类后,再找出发送事件的地方,就可以搞清楚SpringBoot是怎么加载配置文件的了,SpringBoot在启动之前先初始化好SpringApplicationRunListeners这个类,它会实现SpringApplicationRunListener接口然后对事件进行转发:
class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } ... }
获取SpringApplicationRunListeners的代码如下:
private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); }
同样也会去加载
spring.factories文件,该文件有一个EventPublishingRunListener类,该类的作用就是SpringBoot的事件转换成ApplicationEvent发送出去。
# Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
小结
- SpringBoot会将事件转换成ApplicationEvent再分发
- SpringBoot是通过监听
ApplicationEnvironmentPreparedEvent
事件来加载配置文件的 - ConfigFileApplicationListener是处理配置文件的主要类
SpringBoot从哪些地方加载配置文件?
上面已经分析到ConfigFileApplicationListener是处理配置文件的主要类,然后进一步的查看SpringBoot是从哪些地址加载配置文件,进入ConfigFileApplicationListener类后会有两个默认的常量:
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "application";
首先在没有任何配置的情况下,会从
DEFAULT_SEARCH_LOCATIONS常量列出来的位置中加载文件名为
DEFAULT_NAMES(.properties或yml)的文件,默认位置包括:
- classpath根目录(classpath:/)
- classpath里面的config文件目录(classpath:/config/)
- 程序运行目录(file:./)
- 程序运行目录下的config目录(file:./config/)
上面说的是没有额外配置的情况,SpringBoot足够灵活可以指定配置文件搜索路径、配置文件名,在ConfigFileApplicationListener类中有个
getSearchLocations方法,它主要负责获取配置搜索目录:
private Set<String> getSearchLocations() { if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(CONFIG_LOCATION_PROPERTY); } Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; }
它的操作步骤大致如下:
- 检查是否有
spring.config.location
属性,如果存在则直接使用它的值 - 从
spring.config.additional-location
属性中获取搜索路径 - 将默认搜索路径添加到搜索集合
这里就可以确定SpringBoot配置的搜索路径有两种情况:如果配置了
spring.config.location则直接使用,否则使用
spring.config.additional-location的属性值 + 默认搜索路径。
SpringBoot是如何支持yaml
和properties
类型的配置文件?
SpringBoot的配置支持
properties和
yaml文件,SpringBoot是如何解析这两种文件的呢,继续分析
ConfigFileApplicationListener这个类,里面有个子类叫
Loader加载配置文件主要的工作就是由这货负责,但是直接读取
properties和
yaml并转换成
PropertySource还是由里面的
PropertySourceLoader负责:
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { ... this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); }
构造
Loader对象的时候就会先加载PropertySourceLoader,加载方式还是从
spring.factories中读取:
# PropertySource Loader aec s org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader
其中配置了两个PropertySourceLoader的实现类:
- PropertiesPropertySourceLoader
- YamlPropertySourceLoader
看名字就知道是分别负责
properties和
yaml的啦。
如果要支持json
配置应该如何做?
如果不喜欢
properties和
yaml这两种格式,想要定义
json做为配置文字格式可以直接定义json类型的PropertySourceLoader:
public class JSONPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] {"json"}; } @Override public List<PropertySource<?>> load(String name, Resource resource) throws IOException { if(resource == null || !resource.exists()){ return Collections.emptyList(); } Map<String, Object> configs = JSON.parseObject(resource.getInputStream(), Map.class); return Collections.singletonList( new MapPropertySource(name, configs) ); } }
然后在
resources目录里面建立个
META-INF,再添加个
spring.factories里面的内容如下:
org.springframework.boot.env.PropertySourceLoader=\ com.csbaic.arch.spring.env.loader.JSONPropertySourceLoader
最后在
resources目录里面建个
application.json的配置文件 :
{ "spring.application.name": "JSONConfig" }
正常启动SpringBoot获取
spring.applicaiton.name的配置的值就是
JSONConfig:
2019-11-02 14:50:17.730 INFO 55275 --- [ main] c.c.a.spring.env.SpringEnvApplication : JSONConfig
SpringBoot的配置优先级是怎么样的?
SpringBoot中有个
PropertySource接口,专门用来保存属性常见的实现类有:
- CommandLinePropertySource
- MapPropertySource
- SystemEnvironmentPropertySource
- ....
另外为了集中管理
PropertySource还抽象出一个
PropertySources接口,PropertySources就一个实现类叫:MutablePropertySources,它将所有的PropertySource都放置在一个名叫
propertySourceList集合中,同时提供一些修改操 1062 作方法:
public void addFirst(PropertySource<?> propertySource) {} public void addLast(PropertySource<?> propertySource) {} public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {} public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {} public int precedenceOf(PropertySource<?> propertySource) { } public PropertySource<?> remove(String name) {} public void replace(String name, PropertySource<?> propertySource) {}
所有的PropertySource都保存在propertySourceList中,越小的索引优先级越高,所以如果想要覆盖属性只要保证优化级够高就行。
placeholder是如何被解析的?
继续分析
ConfigFileApplicationListener的
Loader子类,在构造时还会创建一个
PropertySourcesPlaceholdersResolver,placeholder的解析都由它来完成:
Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment); }
分析PropertySourcesPlaceholdersResolver发现,真正完成解析是由
PropertyPlaceholderHelper完成,PropertySourcesPlaceholdersResolver 在构造的时候就会创建一个PropertyPlaceholderHelper
public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) { this.sources = sources; this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true); }
PropertySourcesPlaceholdersResolver 在创建 PropertyPlaceholderHelper 的时候会传递三个参数:前缀、后缀、默认值分割符,分别由以下三个常量表示:
public static final String PLACEHOLDER_PREFIX = "${"; public static final String PLACEHOLDER_SUFFIX = "}"; public static final String VALUE_SEPARATOR = ":";
这样 PropertyPlaceholderHelper 在解析placeholder时就能知道以什么格式来解析比如:${spring.application.name}这个placeholder就会被解析成属性值。
总结
SpringBoot的配置非常灵活配置可以来自文件、环境变量、JVM系统属性、配置中心等等,SpringBoot通过
PropertySource和
PropertySources实现属性优先级、CRUD的统一管理,为开发者提供统一的配置抽象。
《架构文摘》每天一篇架构领域重磅好文,涉及一线互联网公司应用架构(高可用、高性 能、高稳定)、大数据、机器学习等各个热门领域。
- spring-boot 如何加载rsources下面的自定义配置文件
- Spring如何利用XmlBeanFactory类加载bean的配置文件?
- spring-boot 加载本地静态资源文件路径配置
- springBoot 如何将properties文件属性配置到class中
- spring-boot 加载本地静态资源文件路径配置
- springboot 1.5.2 自定义的配置文件加载
- SpringBoot学习之路:12.Spring Boot使用idea开发如何配置热加载
- springBoot项目加载外置的配置文件
- spring boot 加载本地静态资源文件路径配置
- SpringBoot如何导入自定义配置的yml文件
- springboot配置文件加载不到的问题
- springboot如何读取配置文件中的参数(例如:application-consts.properties) 又结合maven读取配置文件的顺序
- Spring Boot使用profile如何配置不同环境的配置文件
- springboot之@ConfigurationProperties加载配置文件
- springboot(4.1)配置文件加载位置和优先级
- spring boot启动时加载外部配置文件的方法
- SpringBoot如何导入自定义配置的yml文件
- SpringBoot如何导入自定义配置的yml文件
- 监听器如何获取Spring配置文件(加载生成Spring容器)
- springboot如何读取配置文件(application.yml)中的属性值