Spring Boot启动会扫描application.properties或者application.yml文件作为Spring Boot的默认配置文件。在使用过程中会涉及到各种各样的配置,本篇则主要针对配置路径,看一下文件加载的前后顺序。多个配置文件配置了同样得值,会加载哪一个呢?
一、内部配置的加载顺序 1)配置文件前后顺序执行测试-
file:/config/
-
file:/
-
classpath:/config/
-
classpath:/
以上顺序按照优先级从高到低的顺序,所有位置的文件都会被加载,高优先级的配置内容会覆盖低优先级配置的内容,其中配置文件中的内容是互补配置,即 -
存在相同的配置内容,高优先级的内容会覆盖低优先级的内容
-
存在不同的内容的时候,高优先级和低优先级的配置内容取并集
我配置了如上几个配置文件,看项目启动结果,则可看到最终生效的配置文件。
第一种情况:五个路径配置文件都存在
- file:/config/
- file:/
- classpath:/config/
- classpath:/
结果为:
第二种:去除8025所在的file:/config/
- file:/
- classpath:/config/
- classpath:/
结果为:
第三种:再去除8026所在的file:/
- classpath:/config/
- classpath:/
结果为:
第四种:再去除8024所在的配置文件,则只剩 classpath:/下的.properties和.yml两个配置文件了,看执行结果会如何呢?
二、外部配置的加载顺序
- classpath:/
结果为:
SpringBoot 既可以加载指定目录下的配置文件获取配置项,也可以通过启动参数(VM Options)传入配置项,为什么通过启动参数传入的配置项会“顶掉”配置文件中的配置?
三、prepareEnvironment原理解析run方法下有如下方法:字面理解就是准备环境
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { //获取或创建环境(存在直接返回,不存在创建) ConfigurableEnvironment environment = this.getOrCreateEnvironment(); //配置环境:配置PropertySources和activeProfiles this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs()); //通知所有的监听者,环境已经准备好了 listeners.environmentPrepared((ConfigurableEnvironment)environment); // bindToSpringApplication绑定环境 this.bindToSpringApplication((ConfigurableEnvironment)environment); // 如果是非web环境,将环境转换成StandardEnvironment if (!this.isCustomEnvironment) { environment = (new EnvironmentConverter(this.getClassLoader())).convertEnvironmentIfNecessary((ConfigurableEnvironment)environment, this.deduceEnvironmentClass()); } // 如果配置了configurationProperties属性, 那么将其放在environment的propertySources的首部 ConfigurationPropertySources.attach((Environment)environment); return (ConfigurableEnvironment)environment; }1)getOrCreateEnvironment
getOrCreateEnvironment方法创建并返回了一个环境:
private ConfigurableEnvironment getOrCreateEnvironment() { // 存在则直接返回 if (this.environment != null) { return this.environment; } else { // 根据webApplicationType创建对应的Environment switch(this.webApplicationType) { // 标准的Servlet环境,也就是我们说的web环境 case SERVLET: return new StandardServletEnvironment(); case REACTIVE: //响应式web环境,2.0新引入 return new StandardReactiveWebEnvironment(); default: //普通程序 return new StandardEnvironment(); } } }
我们断点调试,则是返回StandardServletEnvironment
该环境目前包含的内容如下
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { //默认为TRUE if (this.addConversionService) { //environment中配置各个转换类 ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService)conversionService); } //配置属性 this.configurePropertySources(environment, args); //配置profile this.configureProfiles(environment, args); }
从源码看,将配置任务按顺序委托给configurePropertySources和configureProfiles,分别配置属性和profile。
我们来看看configureProfiles,正常项目中,我们是分各种环境的,比如开发环境,测试环境,线上环境,有可能还有什么压测环境等等。就是通过此方法来查找对应的环境配置。
protected void configureProfiles(ConfigurableEnvironment environment, String[] args) { // 保证environment的activeProfiles属性被初始化了。从PropertySources中查找spring.profiles.active属性 environment.getActiveProfiles(); Setprofiles = new linkedHashSet(this.additionalProfiles); //再次获取和配置 profileprofiles.addAll(Arrays.asList(environment.getActiveProfiles())); //设置environment的profile environment.setActiveProfiles(StringUtils.toStringArray(profiles)); }
configureEnvironment已经配置好了环境,下一步则是发送出去
3)listeners.environmentPrepared调用SpringApplicationRunListeners的environmentPrepared方法发布事件。该方法会遍历注册在spring.factories中SpringApplicationRunListener实现类,然后调用其environmentPrepared方法。
每一个自动注入类中都有spring.factories,其中SpringApplicationRunListener实现类注册为:
public void environmentPrepared(ConfigurableEnvironment environment) { Iterator var2 = this.listeners.iterator(); while(var2.hasNext()) { SpringApplicationRunListener listener = (SpringApplicationRunListener)var2.next(); listener.environmentPrepared(environment); } }
其中listeners便是注册的类的集合,这里默认只有EventPublishingRunListener。它的environmentPrepared方法实现为:
public void environmentPrepared(ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(this.application, this.args, environment)); }
其实就是发布了一个监听事件。该事件会被同样注册在spring.factories中的ConfigFileApplicationListener监听到:
关于配置文件的核心处理便在ConfigFileApplicationListener中完成。在该类中我们可以看到很多熟悉的常量:
public class ConfigFileApplicationListener implements EnvironmentPostProcessor, SmartApplicationListener, Ordered { private static final String DEFAULT_PROPERTIES = "defaultProperties"; private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "application"; private static final SetNO_SEARCH_NAMES = Collections.singleton((Object)null); private static final Bindable STRING_ARRAY = Bindable.of(String[].class); public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active"; public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include"; public static final String CONFIG_NAME_PROPERTY = "spring.config.name"; public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location"; public static final String CONFIG_ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location"; public static final int DEFAULT_ORDER = -2147483638; private final DeferredLog logger = new DeferredLog(); private String searchLocations; private String names; private int order = -2147483638; public ConfigFileApplicationListener() { }
比如Spring Boot默认寻找的配置文件的名称、默认扫描的类路径等。
不仅如此,该类还实现了监听器SmartApplicationListener接口和EnvironmentPostProcessor接口也就是拥有了监听器和环境处理的功能。
设置断点调试则会发现,调试到environmentPrepared方法,直接跳转到ConfigFileApplicationListener中的onApplicationEvent上。
onApplicationEvent方法对接收到事件进行判断,如果是ApplicationEnvironmentPreparedEvent事件则调用onApplicationEnvironmentPreparedEvent方法进行处理
如下图:
onApplicationEnvironmentPreparedEvent会获取到对应的EnvironmentPostProcessor并调用其postProcessEnvironment方法进行处理。而loadPostProcessors方法获取的EnvironmentPostProcessor正是在spring.factories中配置的当前类。如下图:
经过一系列的调用,最终调用到该类的如下方法:
protected void addPropertySources(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { RandomValuePropertySource.addToEnvironment(environment); (new ConfigFileApplicationListener.Loader(environment, resourceLoader)).load(); }
其中Loader类为ConfigFileApplicationListener内部类,提供了具体处理配置文件优先级、profile、加载解析等功能。
其中Loader中的Load方法,遍历PropertySourceLoader列表,并进行对应配置文件的解析,而这里的列表中的PropertySourceLoader同样配置在spring.factories中:
private void load(String location, String name, ConfigFileApplicationListener.Profile profile, ConfigFileApplicationListener.documentFilterFactory filterFactory, ConfigFileApplicationListener.documentConsumer consumer) { if (!StringUtils.hasText(name)) { Iterator var6 = this.propertySourceLoaders.iterator(); while(var6.hasNext()) { PropertySourceLoader loader = (PropertySourceLoader)var6.next(); if (this.canLoadFileExtension(loader, location)) { this.load(loader, location, profile, filterFactory.getdocumentFilter(profile), consumer); return; } } } Setprocessed = new HashSet(); Iterator var14 = this.propertySourceLoaders.iterator(); while(var14.hasNext()) { PropertySourceLoader loaderx = (PropertySourceLoader)var14.next(); String[] var9 = loaderx.getFileExtensions(); int var10 = var9.length; for(int var11 = 0; var11 < var10; ++var11) { String fileExtension = var9[var11]; if (processed.add(fileExtension)) { this.loadForFileExtension(loaderx, location + name, "." + fileExtension, profile, filterFactory, consumer); } } } }
#PropertySource Loaders
org.springframework.boot.env.PropertySourceLoader=
org.springframework.boot.env.PropertiesPropertySourceLoader,
org.springframework.boot.env.YamlPropertySourceLoader
查看这两PropertySourceLoader的源代码,会发现SpringBoot默认支持的配置文件格式及解析方法。
这也就明白了最开始测试的时候,内部配置的先后顺序了。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)