SpringBoot加载配置⽂件的完整步骤
这篇⽂章主要给⼤家介绍了关于Spring Boot加载配置⽂件的完整步骤,⽂中通过⽰例代码介绍的⾮常详细,对⼤家的学习或者使⽤Spring Boot具有⼀定的参考学习价值,需要的朋友们下⾯来⼀起学习学习吧
前⾔
本⽂针对版本2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚⼀些常见的问题⽐如:
1. SpringBoot从哪⾥开始加载配置⽂件?
2. SpringBoot从哪些地⽅加载配置⽂件?
3. SpringBoot是如何⽀持yaml和properties类型的配置⽂件?
4. 如果要⽀持json配置应该如何做?
5. SpringBoot的配置优先级是怎么样的?
6. 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
t.ApplicationListener= org.t.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) {
}
}
...
}
获取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.t.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 (ainsProperty(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;
}
它的操作步骤⼤致如下:
1. 检查是否有fig.location属性,如果存在则直接使⽤它的值
2. 从fig.additional-location属性中获取搜索路径
3. 将默认搜索路径添加到搜索集合
这⾥就可以确定SpringBoot配置的搜索路径有两种情况:如果配置了fig.location则直接使⽤,否则使⽤fig.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());
}
spring启动流程面试回答构造Loader对象的时候就会先加载PropertySourceLoader,加载⽅式还是从spring.factories中读取:
# PropertySource Loaders
org.v.PropertySourceLoader=\
org.v.PropertiesPropertySourceLoader,\
org.v.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 || !ists()){
ptyList();
}
Map<String, Object> configs = JSON.InputStream(), Map.class);
return Collections.singletonList(
new MapPropertySource(name, configs)
);
}
}
然后在resources⽬录⾥⾯建⽴个META-INF,再添加个spring.factories⾥⾯的内容如下:
org.v.PropertySourceLoader=\
com.csbaic.v.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.v.SpringEnvApplication : JSONConfig
SpringBoot的配置优先级是怎么样的?
SpringBoot中有个PropertySource接⼝,专门⽤来保存属性常见的实现类有:
CommandLinePropertySource
MapPropertySource
SystemEnvironmentPropertySource
....
另外为了集中管理PropertySource还抽象出⼀个PropertySources接⼝,PropertySources就⼀个实现类叫:MutablePropertySources,它将所有的PropertySource都放置在⼀个名叫propertySourceList集合中,同时提供⼀些修改操作⽅法:
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 vironment);
}
分析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的统⼀管理,为开发者提供统⼀的配置抽象。本⽂转⾃:
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论