Spring Boot加载配置文件的完整步骤
前言
本文针对版本2.2.0.RELEASE来分析SpringBoot的配置处理源码,通过查看SpringBoot的源码来弄清楚一些常见的问题比如:
- SpringBoot从哪里开始加载配置文件?
- SpringBoot从哪些地方加载配置文件?
- SpringBoot是如何支持yaml和properties类型的配置文件?
- 如果要支持json配置应该如何做?
- SpringBoot的配置优先级是怎么样的?
- placeholder是如何被解析的?
带着我们的问题一起去看一下SpringBoot配置相关的源代码,找出问题的答案。
SpringBoot从哪里开始加载配置文件?
SpringBoot加载配置文件的入口是由ApplicationEnvironmentPreparedEvent事件进入的,SpringBoot会在SpringApplication的构造函数中通过spring.factories文件获取ApplicationListener的实例类:
publicSpringApplication(ResourceLoaderresourceLoader,Class>...primarySources){ ... setListeners((Collection)getSpringFactoriesInstances(ApplicationListener.class)); ... }
spring.factories中有一个ConfigFileApplicationListener类,它会监听ApplicationEnvironmentPreparedEvent然后再加载配置文件:
#ApplicationListeners org.springframework.context.ApplicationListener=org.springframework.boot.context.config.ConfigFileApplicationListener ...
有了事件和事件处理的类后,再找出发送事件的地方,就可以搞清楚SpringBoot是怎么加载配置文件的了,SpringBoot在启动之前先初始化好SpringApplicationRunListeners这个类,它会实现SpringApplicationRunListener接口然后对事件进行转发:
classSpringApplicationRunListeners{ privatefinalLoglog; privatefinalListlisteners; SpringApplicationRunListeners(Loglog,Collectionlisteners){ this.log=log; this.listeners=newArrayList<>(listeners); } voidenvironmentPrepared(ConfigurableEnvironmentenvironment){ for(SpringApplicationRunListenerlistener:this.listeners){ listener.environmentPrepared(environment); } } ... }
获取SpringApplicationRunListeners的代码如下:
privateSpringApplicationRunListenersgetRunListeners(String[]args){ Class>[]types=newClass>[]{SpringApplication.class,String[].class}; returnnewSpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class,types,this,args)); }
同样也会去加载spring.factories文件,该文件有一个EventPublishingRunListener类,该类的作用就是SpringBoot的事件转换成ApplicationEvent发送出去。
#RunListeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener
小结
- SpringBoot会将事件转换成ApplicationEvent再分发
- SpringBoot是通过监听ApplicationEnvironmentPreparedEvent事件来加载配置文件的
- ConfigFileApplicationListener是处理配置文件的主要类
SpringBoot从哪些地方加载配置文件?
上面已经分析到ConfigFileApplicationListener是处理配置文件的主要类,然后进一步的查看SpringBoot是从哪些地址加载配置文件,进入ConfigFileApplicationListener类后会有两个默认的常量:
privatestaticfinalStringDEFAULT_SEARCH_LOCATIONS="classpath:/,classpath:/config/,file:./,file:./config/"; privatestaticfinalStringDEFAULT_NAMES="application";
首先在没有任何配置的情况下,会从DEFAULT_SEARCH_LOCATIONS常量列出来的位置中加载文件名为DEFAULT_NAMES(.properties或yml)的文件,默认位置包括:
- classpath根目录(classpath:/)
- classpath里面的config文件目录(classpath:/config/)
- 程序运行目录(file:./)
- 程序运行目录下的config目录(file:./config/)
上面说的是没有额外配置的情况,SpringBoot足够灵活可以指定配置文件搜索路径、配置文件名,在ConfigFileApplicationListener类中有个getSearchLocations方法,它主要负责获取配置搜索目录:
privateSetgetSearchLocations(){ if(this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)){ returngetSearchLocations(CONFIG_LOCATION_PROPERTY); } Set locations=getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations,DEFAULT_SEARCH_LOCATIONS)); returnlocations; }
它的操作步骤大致如下:
- 检查是否有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(ConfigurableEnvironmentenvironment,ResourceLoaderresourceLoader){ ... this.propertySourceLoaders=SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); }
构造Loader对象的时候就会先加载PropertySourceLoader,加载方式还是从spring.factories中读取:
#PropertySourceLoaders 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:
publicclassJSONPropertySourceLoaderimplementsPropertySourceLoader{ @Override publicString[]getFileExtensions(){ returnnewString[]{"json"}; } @Override publicList>load(Stringname,Resourceresource)throwsIOException{ if(resource==null||!resource.exists()){ returnCollections.emptyList(); } Map configs=JSON.parseObject(resource.getInputStream(),Map.class); returnCollections.singletonList( newMapPropertySource(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-0214:50:17.730 INFO55275---[ main]c.c.a.spring.env.SpringEnvApplication :JSONConfig
SpringBoot的配置优先级是怎么样的?
SpringBoot中有个PropertySource接口,专门用来保存属性常见的实现类有:
- CommandLinePropertySource
- MapPropertySource
- SystemEnvironmentPropertySource
- ....
另外为了集中管理PropertySource还抽象出一个PropertySources接口,PropertySources就一个实现类叫:MutablePropertySources,它将所有的PropertySource都放置在一个名叫propertySourceList集合中,同时提供一些修改操作方法:
publicvoidaddFirst(PropertySource>propertySource){} publicvoidaddLast(PropertySource>propertySource){} publicvoidaddBefore(StringrelativePropertySourceName,PropertySource>propertySource){} publicvoidaddAfter(StringrelativePropertySourceName,PropertySource>propertySource){} publicintprecedenceOf(PropertySource>propertySource){} publicPropertySource>remove(Stringname){} publicvoidreplace(Stringname,PropertySource>propertySource){}
所有的PropertySource都保存在propertySourceList中,越小的索引优先级越高,所以如果想要覆盖属性只要保证优化级够高就行。
placeholder是如何被解析的?
继续分析ConfigFileApplicationListener的Loader子类,在构造时还会创建一个PropertySourcesPlaceholdersResolver,placeholder的解析都由它来完成:
Loader(ConfigurableEnvironmentenvironment,ResourceLoaderresourceLoader){ this.placeholdersResolver=newPropertySourcesPlaceholdersResolver(this.environment); }
分析PropertySourcesPlaceholdersResolver发现,真正完成解析是由PropertyPlaceholderHelper完成,PropertySourcesPlaceholdersResolver在构造的时候就会创建一个PropertyPlaceholderHelper
publicPropertySourcesPlaceholdersResolver(Iterable>sources,PropertyPlaceholderHelperhelper){ this.sources=sources; this.helper=(helper!=null)?helper:newPropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX,SystemPropertyUtils.VALUE_SEPARATOR,true); }
PropertySourcesPlaceholdersResolver在创建PropertyPlaceholderHelper的时候会传递三个参数:前缀、后缀、默认值分割符,分别由以下三个常量表示:
publicstaticfinalStringPLACEHOLDER_PREFIX="${"; publicstaticfinalStringPLACEHOLDER_SUFFIX="}"; publicstaticfinalStringVALUE_SEPARATOR=":";
这样PropertyPlaceholderHelper在解析placeholder时就能知道以什么格式来解析比如:${spring.application.name}这个placeholder就会被解析成属性值。
总结
SpringBoot的配置非常灵活配置可以来自文件、环境变量、JVM系统属性、配置中心等等,SpringBoot通过
PropertySource和PropertySources实现属性优先级、CRUD的统一管理,为开发者提供统一的配置抽象。
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对毛票票的支持。
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。