前言
postProcessEnvironment
总结
前言
在使用Springboot项目时,我们或多或少会使用到spring.profiles.active,spring.config.location等配置。那你知道两者的区别吗,如果项目中配置文件很多时,如下面这种情况,配置文件的加载顺序又是怎样?
先说结论:
2.如果使用了spring.config.location
则不会再加载其他application.properties或application.yaml,这就要求我们用spring.config.location指定的配置必须包含项目需要的所有配置
1.新旧版本对比
1.1 Springboot<2.4 ConfigFileApplicationListener
我们打开ConfigFileApplicationListener类
1.2 Springboot>2.4 ConfigDataEnvironmentPostProcessor
这里的顺序倒过来则是最终配置文件的优先级顺序。
接下里我们从源码角度分析springboot是如何加载配置文件的。
2.Springboot 配置文件加载
本篇基于springboot 2.5.14版本分析。
我们继续从Springboot启动源码入手。
#SpringApplication.class
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,
DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
//❶
// Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment();
//❷
configureEnvironment(environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach(environment);
//❸
listeners.environmentPrepared(bootstrapContext, environment);
DefaultPropertiesPropertySource.moveToEnd(environment);
Assert.state(!environment.containsProperty("spring.main.environment-prefix"),
"Environment prefix cannot be set via properties.");
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = convertEnvironment(environment);
}
ConfigurationPropertySources.attach(environment);
return environment;
}
Springboot启动后,首先是准备Springboot启动所需要的环境配置。
2.1 getOrCreateEnvironment
创建并且配置环境
#SpringApplication.class
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationServletEnvironment();
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
我们使用springboot来构建项目一般都是web环境,目前主流还是用的servlet,所以会创建ApplicationServletEnvironment
#SpringApplication.class
private ConfigurableEnvironment getOrCreateEnvironment() {
if (this.environment != null) {
return this.environment;
}
switch (this.webApplicationType) {
case SERVLET:
return new ApplicationServletEnvironment();
case REACTIVE:
return new ApplicationReactiveWebEnvironment();
default:
return new ApplicationEnvironment();
}
}
先看下它的依赖关系
然后我们再follow无参构造,依次往父类跟踪,最终我们在AbstractEnvironment中发现了无参构造,隐藏的还算比较深。
#AbstractEnvironment
public AbstractEnvironment() {
this(new MutablePropertySources());
}
protected AbstractEnvironment(MutablePropertySources propertySources) {
this.propertySources = propertySources;
this.propertyResolver = createPropertyResolver(propertySources);
customizePropertySources(propertySources);
}
主要是创建了MutablePropertySources对象,然后调用可以重写的方法customizePropertySources来个性化配置PropertySources,
后面解析出来的配置都会通过addFirst
或者addLast
来调整配置的优先级
接着我们看customizePropertySources,我们发现
ApplicationServletEnvironment的父类StandardServletEnvironment实现了该方法,如果你看过springboot以前的源码会发现
ApplicationServletEnvironment是后面新增的一个类。
#StandardServletEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(new StubPropertySource(SERVLET_CONFIG_PROPERTY_SOURCE_NAME));
propertySources.addLast(new StubPropertySource(SERVLET_CONTEXT_PROPERTY_SOURCE_NAME));
if (jndiPresent && JndiLocatorDelegate.isDefaultJndiEnvironmentAvailable()) {
propertySources.addLast(new JndiPropertySource(JNDI_PROPERTY_SOURCE_NAME));
}
super.customizePropertySources(propertySources);
}
可以看到它主要是添加servletConfigInitParams和servletContextInitParams的PropertySources配置
完事后它调了父类的customizePropertySources方法,我们跟踪代码会发现父类StandardEnvironment中也实现了该方法。
#StandardEnvironment
@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
propertySources.addLast(
new PropertiesPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
propertySources.addLast(
new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
}
可以看到它主要添加了systemProperties和systemEnvironmen系统配置,并且此时同时读取了系统的配置属性getSystemProperties(),getSystemEnvironment()
从上面也可以看出spring的抽象做的很好,职责也很单一,不同的类加载不同的配置。
2.2 configureEnvironment
顾名思义,这个也是继续配置环境
#SpringApplication
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) {
if (this.addConversionService) {
environment.setConversionService(new ApplicationConversionService());
}
configurePropertySources(environment, args);
configureProfiles(environment, args);
}
继续看configurePropertySources方法
#SpringApplication
protected void configurePropertySources(ConfigurableEnvironment environment, String[] args) {
MutablePropertySources sources = environment.getPropertySources();
if (!CollectionUtils.isEmpty(this.defaultProperties)) {
DefaultPropertiesPropertySource.addOrMerge(this.defaultProperties, sources);
}
if (this.addCommandLineProperties && args.length > 0) {
String name = CommandLinePropertySource.COMMAND_LINE_PROPERTY_SOURCE_NAME;
if (sources.contains(name)) {
PropertySource<?> source = sources.get(name);
CompositePropertySource composite = new CompositePropertySource(name);
composite.addPropertySource(
new SimpleCommandLinePropertySource("springApplicationCommandLineArgs", args));
composite.addPropertySource(source);
sources.replace(name, composite);
}
else {
sources.addFirst(new SimpleCommandLinePropertySource(args));
}
}
}
如果我们有在启动命令里面添加启动参数,那么就会使用addFirst创建SimpleCommandLinePropertySource,优先级最高。
经过前面两步的初始化配置,目前我们的environment主要有如下几个配置(因为没有在命令行添加启动参数,所以没有SimpleCommandLinePropertySource)
2.3 listeners.environmentPrepared
前面已经初始化了一些基础配置后,这步开始发布环境准备好的事件。这里面也用了一系列的抽象以及函数式编程,我们直接到关键类
#EventPublishingRunListener
@Override
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
发布ApplicationEnvironmentPreparedEvent事件,然后调用SimpleApplicationEventMulticaster的doInvokeListener方法,
#SimpleApplicationEventMulticaster
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
xxx
}
}
这里其实就是spring event的源码了。(下次单独分析这块,很多人对event肯定存在误解,event其实不一定是异步,主要是解耦。)
最后调用EnvironmentPostProcessorApplicationListener的方法onApplicationEvent
#EnvironmentPostProcessorApplicationListener
@Override
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent();
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent();
}
}
继续跟踪onApplicationEnvironmentPreparedEvent
#EnvironmentPostProcessorApplicationListener
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(application.getResourceLoader(),
event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
然后通过processor开始正式的加工环境变量了。
以上步骤总结:
3.postProcessEnvironment
#ConfigDataEnvironmentPostProcessor
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
resourceLoader = (resourceLoader != null) ? resourceLoader : new DefaultResourceLoader();
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles).processAndApply();
}
catch (UseLegacyConfigProcessingException ex) {
xxx
}
}
3.1 getConfigDataEnvironment
#ConfigDataEnvironmentPostProcessor
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles, this.environmentUpdateListener);
}
这里创建了ConfigDataEnvironment,我们在开头有介绍,这里面有包含我们默认的配置路径。
#ConfigDataEnvironment
ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
//❶获取绑定对象
Binder binder = Binder.get(environment);
UseLegacyConfigProcessingException.throwIfRequested(binder);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
.orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
//❷初始化resolver
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
: ConfigDataEnvironmentUpdateListener.NONE;
//❸初始化loaders
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
//❹创建contributors
this.contributors = createContributors(binder);
}
❶Binder.get(environment),配置绑定类Binder的功能非常强大。是Springboot2.0提供的数据绑定新特性,比Environment类好用很多,可以非常方便地进行类型转换,可以将属性绑定到对象,Map,List等类型上。虽然我们可以从environment中拿到很多配置,但有些时候比如我们通过map注入的属性,key值是无法确定的,这个时候就可以用Binder来获取map对象,然后再做其他判断操作了。(后面再单独讲解Binder如何使用)。
❷createConfigDataLocationResolvers,初始化resolvers
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) {
return new ConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
}
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader) {
this(logFactory, bootstrapContext, binder, resourceLoader, SpringFactoriesLoader
.loadFactoryNames(ConfigDataLocationResolver.class, resourceLoader.getClassLoader()));
}
看到SpringFactoriesLoader.loadFactoryNames我们就知道它是从WEB-INF目录下的spring.facotries文件中寻找对应的配置了。如果我们自定义resolvers,直接在spring.factories文件中配置即可,这也是另外一种扩展方式。最终ConfigTreeConfigDataLocationResolver和StandardConfigDataLocationResolver会被扫描注入IOC。
❸new ConfigDataLoaders,初始化loaders,和初始化resolver一样,最终会创建ConfigTreeConfigDataLoader和StandardConfigDataLoader。
❹createContributors,创建contributors
#ConfigDataEnvironment
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Building config data environment contributors");
//❶取出propertySources准备继续装配
MutablePropertySources propertySources = this.environment.getPropertySources();
List<ConfigDataEnvironmentContributor> contributors = new ArrayList<>(propertySources.size() + 10);
PropertySource<?> defaultPropertySource = null;
for (PropertySource<?> propertySource : propertySources) {
if (DefaultPropertiesPropertySource.hasMatchingName(propertySource)) {
defaultPropertySource = propertySource;
}
else {
this.logger.trace(LogMessage.format("Creating wrapped config data contributor for '%s'",
propertySource.getName()));
//❷填充contributors
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
}
}
//❸获取初始化importer配置
contributors.addAll(getInitialImportContributors(binder));
if (defaultPropertySource != null) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
return createContributors(contributors);
}
❶this.environment.getPropertySources(),在最开始prepareEnvrionment那步已经预先填充了一些propertySources,这里取出来继续填充。
❷ConfigDataEnvironmentContributor,主要是通过propertySources来构建ConfigDataEnvironmentContributor对象,这个对象很重要,在后面配置的解析中主要靠它来做判断的。
❸getInitialImportContributors(binder),获取初始化import的配置
#ConfigDataEnvironment
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
addInitialImportContributors(initialContributors, bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
addInitialImportContributors(initialContributors,
bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {
return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
}
同样来个总结。
3.2 processAndApply
收集并加载配置文件
#ConfigDataEnvironment
/**
* Process all contributions and apply any newly imported property sources to the
* {@link Environment}.
*/
void processAndApply() {
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers,
this.loaders);
registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
//❶初始化process
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
ConfigDataActivationContext activationContext = createActivationContext(
contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
//❷激活cloudPlatform
contributors =processWithoutProfiles(contributors, importer, activationContext);
//❸添加additionalProfiles和include_profile
activationContext = withProfiles(contributors, activationContext);
//❹最终处理所有profile,更新Kind类型
contributors =processWithProfiles(contributors, importer, activationContext);
//❺ 应用到环境中
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),
importer.getOptionalLocations());
}
3.2.1 processInitial
#ConfigDataEnvironment
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer) {
this.logger.trace("Processing initial config data environment contributors without activation context");
contributors = contributors.withProcessedImports(importer, null);
registerBootstrapBinder(contributors, null, DENY_INACTIVE_BINDING);
return contributors;
}
/**
* The various kinds of contributor.
*/
enum Kind {
/**
* A root contributor used contain the initial set of children.
*/
ROOT,
/**
* An initial import that needs to be processed.
*/
INITIAL_IMPORT,
/**
* An existing property source that contributes properties but no imports.
*/
EXISTING,
/**
* A contributor with {@link ConfigData} imported from another contributor but not
* yet bound.
*/
UNBOUND_IMPORT,
/**
* A contributor with {@link ConfigData} imported from another contributor that
* has been.
*/
BOUND_IMPORT,
/**
* A valid location that contained nothing to load.
*/
EMPTY_LOCATION;
}
正常情况下一个存在的配置的状态迁移过程就是INITIAL_IMPORT –>UNBOUND_IMPORT –> BOUND_IMPORT
3.2.2 processWithoutProfiles
#ConfigDataEnvironment
private ConfigDataEnvironmentContributors processWithoutProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
this.logger.trace("Processing config data environment contributors with initial activation context");
contributors = contributors.withProcessedImports(importer, activationContext);
registerBootstrapBinder(contributors, activationContext, DENY_INACTIVE_BINDING);
return contributors;
}
#ConfigDataActivationContext
ConfigDataActivationContext(Environment environment, Binder binder) {
this.cloudPlatform = deduceCloudPlatform(environment, binder);
this.profiles = null;
}
如果涉及到云平台,这里就会处理云平台相关配置。
3.2.3 withProfiles
#ConfigDataEnvironment
private ConfigDataActivationContext withProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext) {
Binder binder = contributors.getBinder(activationContext,
(contributor) -> !contributor.hasConfigDataOption(ConfigData.Option.IGNORE_PROFILES),
BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE);
try {
Set<String> additionalProfiles = new LinkedHashSet<>(this.additionalProfiles);
additionalProfiles.addAll(getIncludedProfiles(contributors, activationContext));
Profiles profiles = new Profiles(this.environment, binder, additionalProfiles);
return activationContext.withProfiles(profiles);
}
}
这步主要是添加additionalProfiles和include_profile配置
3.2.4 processWithProfiles
最后一步处理contributors配置的状态
#ConfigDataEnvironment
private ConfigDataEnvironmentContributors processWithProfiles(ConfigDataEnvironmentContributors contributors,
ConfigDataImporter importer, ConfigDataActivationContext activationContext) {
this.logger.trace("Processing config data environment contributors with profile activation context");
contributors = contributors.withProcessedImports(importer, activationContext);
registerBootstrapBinder(contributors, activationContext, ALLOW_INACTIVE_BINDING);
return contributors;
}
3.2.5 withProcessedImports
#ConfigDataEnvironmentContributors
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
ImportPhase importPhase = ImportPhase.get(activationContext);
this.logger.trace(LogMessage.format("Processing imports for phase %s. %s", importPhase,
(activationContext != null) ? activationContext : "no activation context"));
ConfigDataEnvironmentContributors result = this;
int processed = 0;
while (true) {
//获取nextToProcess
ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result;
}
if (contributor.getKind() == Kind.UNBOUND_IMPORT) {
//如果是UNBOUND_IMPORT,绑定配置
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(result, activationContext);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, bound));
continue;
}
ConfigDataLocationResolverContext locationResolverContext = new ContributorConfigDataLocationResolverContext(
result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ContributorDataLoaderContext(this);
//❶获取ConfigDataLocation
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
//❷解析configDataLocation并加载为configData
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
❶contributor.getImports() 获取ConfigDataLocation,即默认配置等的路径。
❷importer.resolveAndLoad,解析configDataLocation并加载为configData,继续跟踪代码
#ConfigDataImporter
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
List<ConfigDataLocation> locations) {
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
//❶解析配置
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
//❷加载配置
return load(loaderContext, resolved);
}
#StandardConfigDataLocationResolver
public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context,
ConfigDataLocation location) throws ConfigDataNotFoundException {
return resolve(getReferences(context, location.split()));
}
其中getReferences就是获取配置文件可能存在的所有路径
如classpath路径下
项目同级目录下
接下来调用reslove方法来获取有效的地址
#StandardConfigDataLocationResolver
private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {
if (!this.resourceLoader.isPattern(reference.getResourceLocation())) {
return resolveNonPattern(reference);
}
return resolvePattern(reference);
}
接着
private List<StandardConfigDataResource> resolvePattern(StandardConfigDataReference reference) {
List<StandardConfigDataResource> resolved = new ArrayList<>();
for (Resource resource : this.resourceLoader.getResources(reference.getResourceLocation(), ResourceType.FILE)) {
if (!resource.exists() && reference.isSkippable()) {
logSkippingResource(reference);
}
else {
resolved.add(createConfigResourceLocation(reference, resource));
}
}
return resolved;
}
通过resourceLoader去加载指定路径的配置,这样就可以判断是否匹配了。
接下来再回到❷load(loaderContext, resolved),加载指定配置
#StandardConfigDataLoader
public ConfigData load(ConfigDataLoaderContext context, StandardConfigDataResource resource)
throws IOException, ConfigDataNotFoundException {
if (resource.isEmptyDirectory()) {
return ConfigData.EMPTY;
}
ConfigDataResourceNotFoundException.throwIfDoesNotExist(resource, resource.getResource());
StandardConfigDataReference reference = resource.getReference();
Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),
Origin.from(reference.getConfigDataLocation()));
String name = String.format("Config resource '%s' via location '%s'", resource,
reference.getConfigDataLocation());
List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);
PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;
return new ConfigData(propertySources, options);
}
前置主要是针对xml,properties文件解析,后者主要是yaml文件解析。
最终返回的是PropertySource
总结一下
3.2.6 applyToEnvironment
#ConfigDataEnvironment
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
Set<ConfigDataLocation> optionalLocations) {
checkForInvalidProperties(contributors);
checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
MutablePropertySources propertySources = this.environment.getPropertySources();
//❶将contributor中的propertySource添加到environment中的propertySource
applyContributor(contributors, activationContext, propertySources);
DefaultPropertiesPropertySource.moveToEnd(propertySources);
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
//❷设置DefaultProfiles
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
//❸设置ActiveProfiles
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
this.environmentUpdateListener.onSetProfiles(profiles);
}
❶applyContributor,将contributor中的propertySource添加到environment中的propertySource
#ConfigDataEnvironment
private void applyContributor(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, MutablePropertySources propertySources) {
this.logger.trace("Applying config data environment contributions");
for (ConfigDataEnvironmentContributor contributor : contributors) {
PropertySource<?> propertySource = contributor.getPropertySource();
if (contributor.getKind() == ConfigDataEnvironmentContributor.Kind.BOUND_IMPORT && propertySource != null) {
if (!contributor.isActive(activationContext)) {
this.logger.trace(
LogMessage.format("Skipping inactive property source '%s'", propertySource.getName()));
}
else {
this.logger
.trace(LogMessage.format("Adding imported property source '%s'", propertySource.getName()));
propertySources.addLast(propertySource);
this.environmentUpdateListener.onPropertySourceAdded(propertySource, contributor.getLocation(),
contributor.getResource());
}
}
}
}
此时的排序如下。
可以看到项目同级目录下config下的有优级要高于其他配置。
如果我们加上了-Dspring.config.location=config/,指定加载项目同级目录config目录下
❷setDefaultProfiles, 设置DefaultProfiles
❸setActiveProfiles,设置ActiveProfiles,可以看到它接收的是一个集合,说明我们可以同时设置多个。
4.总结
属性文件优先级
1.SimpleCommandLinePropertySource 命令行参数
2.java的系统属性,可以通过System.getProperties()获得的内容
3.systemEvironment 操作系统的环境变量
4.application-{profile}.properties (jar包外优先于jar包内)
5.applicaiton.properties (jar包外优先于jar包内)
6.在@Configuration注解修改的类中,通过@PropertySource注解定义的属性。
所有位置的文件都会被加载,高优先级配置内容会覆盖低优先级配置的内容,并形成互补配置。
如果使用了spring.config.location配置,则不会再加载其他application.properties或application.yaml。
同级下,application.properties文件优先级大于application.yml,前者会覆盖后者。
springboot的默认加载路径有5个,分别是classpath:/、classpath:/config/、项目根路径、项目根路径/config/*/、项目根路径/config/,会从这5个路径下加载application.properties或application.yml
5个默认的加载路径的优先级为:项目根路径下的config/*/ > 项目根路径下的config > 项目根路径 > classpath:/config > classpath:/,前者会覆盖后者。(项目根路径可以理解成jar外。)

原文始发于微信公众号(小李的源码图):源码层面分析Springboot 配置文件加载顺序
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145354.html