SpringBoot 版本 2.6.1
Spring项目一般有.properties和.yml两种类型的配置文件,日常工作中知道在resource文件夹中新建这两种类型的文件,Spring框架会将配置文件中的内容自动装载,特别的是SpringBoot自从2.4.0版本以后大改了这部分内容的代码逻辑,原有的关键类ConfigFileApplicationListener被遗弃,因此趁此机会深入新版本的SpringBoot框架源码,看看它在什么时候怎么样去装载这些文件的。
SpringApplication.run函数是一切的起点,我们看看这个函数的实现是怎样的的:
public ConfigurableApplicationContext run(String... args) {
long startTime = System.nanoTime();
DefaultBootstrapContext bootstrapContext = this.createBootstrapContext();
ConfigurableApplicationContext context = null;
this.configureHeadlessProperty();
SpringApplicationRunListeners listeners = this.getRunListeners(args);
listeners.starting(bootstrapContext, this.mainApplicationClass);
try {
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
this.configureIgnoreBeanInfo(environment);
Banner printedBanner = this.printBanner(environment);
context = this.createApplicationContext();
context.setApplicationStartup(this.applicationStartup);
this.prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
this.refreshContext(context);
this.afterRefresh(context, applicationArguments);
Duration timeTakenToStartup = Duration.ofNanos(System.nanoTime() - startTime);
if (this.logStartupInfo) {
(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), timeTakenToStartup);
}
listeners.started(context, timeTakenToStartup);
this.callRunners(context, applicationArguments);
} catch (Throwable var12) {
this.handleRunFailure(context, var12, listeners);
throw new IllegalStateException(var12);
}
try {
Duration timeTakenToReady = Duration.ofNanos(System.nanoTime() - startTime);
listeners.ready(context, timeTakenToReady);
return context;
} catch (Throwable var11) {
this.handleRunFailure(context, var11, (SpringApplicationRunListeners)null);
throw new IllegalStateException(var11);
}
}
在这段代码中,Spring读取配置文件的入口在于:
ConfigurableEnvironment environment = this.prepareEnvironment(listeners, bootstrapContext, applicationArguments);
我们看看preparedEnvironment函数具体的内容:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, DefaultBootstrapContext bootstrapContext, ApplicationArguments applicationArguments) {
ConfigurableEnvironment environment = this.getOrCreateEnvironment();
this.configureEnvironment((ConfigurableEnvironment)environment, applicationArguments.getSourceArgs());
ConfigurationPropertySources.attach((Environment)environment);
listeners.environmentPrepared(bootstrapContext, (ConfigurableEnvironment)environment);
DefaultPropertiesPropertySource.moveToEnd((ConfigurableEnvironment)environment);
Assert.state(!((ConfigurableEnvironment)environment).containsProperty("spring.main.environment-prefix"), "Environment prefix cannot be set via properties.");
this.bindToSpringApplication((ConfigurableEnvironment)environment);
if (!this.isCustomEnvironment) {
environment = this.convertEnvironment((ConfigurableEnvironment)environment);
}
ConfigurationPropertySources.attach((Environment)environment);
return (ConfigurableEnvironment)environment;
}
SpringBoot在环境建立好的时候需要去广播这个事件,那在上面这段代码中监听器EventPublishingRunListener(接口SpringApplicationRunListener的一个实现类)会调用environmentPrepared函数去广播事件。接下来看看这个函数中是如何广播的:
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
this.doWithListeners("spring.boot.application.environment-prepared", (listener) -> {
listener.environmentPrepared(bootstrapContext, environment);
});
}
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
SimpleApplicationEventMulticaster将调用multicastEvent方法:
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
Executor executor = this.getTaskExecutor();
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
this.invokeListener(listener, event);
}
}
}
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
this.doInvokeListener(listener, event);
} catch (Throwable var5) {
errorHandler.handleError(var5);
}
} else {
this.doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
} catch (ClassCastException var6) {
String msg = var6.getMessage();
if (msg != null && !this.matchesClassCastMessage(msg, event.getClass()) && (!(event instanceof PayloadApplicationEvent) || !this.matchesClassCastMessage(msg, ((PayloadApplicationEvent)event).getPayload().getClass()))) {
throw var6;
}
Log loggerToUse = this.lazyLogger;
if (loggerToUse == null) {
loggerToUse = LogFactory.getLog(this.getClass());
this.lazyLogger = loggerToUse;
}
if (loggerToUse.isTraceEnabled()) {
loggerToUse.trace("Non-matching event type for listener: " + listener, var6);
}
}
}
从前面三段代码中我们可以看出,在multicastEvent函数中,会获取到Application中所有的listeners:
Iterator var5 = this.getApplicationListeners(event, type).iterator();
我们读取配置文件的listener就是EnvironmentPostProcessorApplicationListener, 并在之后的doInvokeListener函数中调用这个listener的onApplicationEvent方法。
onApplicationEvent方法在EnvironmentPostProcessorApplicationListener类的实现如下:
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
Iterator var4 = this.getEnvironmentPostProcessors(application.getResourceLoader(), event.getBootstrapContext()).iterator();
while(var4.hasNext()) {
EnvironmentPostProcessor postProcessor = (EnvironmentPostProcessor)var4.next();
postProcessor.postProcessEnvironment(environment, application);
}
}
在onApplicationEnvironmentPreparedEvent函数中,通过getEnvironmentPostProcessors函数获取所有EnvironmentPostProcessor对象的列表,EnvironmentPostProcessor这个类在Spring里的作用我们可以理解成为读取配置文件对外暴露的统一接口,我们可以通过实现这个接口的类来实现读取自定义配置文件。Spring框架中的ConfigDataEnvironmentPostProcessor类就是实现了这个接口用以读取默认的配置文件。
在以上代码中,ConfigDataEnvironmentPostProcessor的实例调用了postProcessEnvironment函数将配置文件的内容读取到环境中,我们来看一下这段代码的实现:
void postProcessEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles) {
try {
this.logger.trace("Post-processing environment to add config data");
ResourceLoader resourceLoader = resourceLoader != null ? resourceLoader : new DefaultResourceLoader();
this.getConfigDataEnvironment(environment, (ResourceLoader)resourceLoader, additionalProfiles).processAndApply();
} catch (UseLegacyConfigProcessingException var5) {
this.logger.debug(LogMessage.format("Switching to legacy config file processing [%s]", var5.getConfigurationProperty()));
this.configureAdditionalProfiles(environment, additionalProfiles);
this.postProcessUsingLegacyApplicationListener(environment, resourceLoader);
}
}
以上代码中,实现读取配置文件并加载进入env中是这一句代码:
this.getConfigDataEnvironment(environment, (ResourceLoader)resourceLoader, additionalProfiles).processAndApply();
getConfigDataEnvironment用以新建一个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(this.getClass());
this.notFoundAction = (ConfigDataNotFoundAction)binder.bind("spring.config.on-not-found", ConfigDataNotFoundAction.class).orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
this.resolvers = this.createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
this.additionalProfiles = additionalProfiles;
this.environmentUpdateListener = environmentUpdateListener != null ? environmentUpdateListener : ConfigDataEnvironmentUpdateListener.NONE;
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext, resourceLoader.getClassLoader());
this.contributors = this.createContributors(binder);
}
接下来processApply()函数将会进行一番骚操作:
void processAndApply() {
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
this.registerBootstrapBinder(this.contributors, (ConfigDataActivationContext)null, DENY_INACTIVE_BINDING);
ConfigDataEnvironmentContributors contributors = this.processInitial(this.contributors, importer);
ConfigDataActivationContext activationContext = this.createActivationContext(contributors.getBinder((ConfigDataActivationContext)null, new BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE}));
contributors = this.processWithoutProfiles(contributors, importer, activationContext);
activationContext = this.withProfiles(contributors, activationContext);
contributors = this.processWithProfiles(contributors, importer, activationContext);
this.applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations());
}
首先提供了一个初始化函数对应默认配置文件的载入,并同时生成一个ConfigDataEnvironmentContributors 的实例:
ConfigDataEnvironmentContributors contributors = this.processInitial(this.contributors, importer);
然后提供了两个函数分别对应有profile和无profile的配置文件的载入:
contributors = this.processWithoutProfiles(contributors, importer, activationContext);
contributors = this.processWithProfiles(contributors, importer, activationContext);
我们先看看初始化的载入是怎么样的:
private ConfigDataEnvironmentContributors processInitial(ConfigDataEnvironmentContributors contributors, ConfigDataImporter importer) {
this.logger.trace("Processing initial config data environment contributors without activation context");
contributors = contributors.withProcessedImports(importer, (ConfigDataActivationContext)null);
this.registerBootstrapBinder(contributors, (ConfigDataActivationContext)null, DENY_INACTIVE_BINDING);
return contributors;
}
其中的关键函数是contributors.withProcessedImports(importer, activationContext),在这段函数中我们终于找到了读取application.properties 和 application.yml文件的关键信息:
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) {
ConfigDataEnvironmentContributor contributor = this.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) {
Iterable<ConfigurationPropertySource> sources = Collections.singleton(contributor.getConfigurationPropertySource());
PlaceholdersResolver placeholdersResolver = new ConfigDataEnvironmentContributorPlaceholdersResolver(result, activationContext, true);
Binder binder = new Binder(sources, placeholdersResolver, (ConversionService)null, (Consumer)null, (BindHandler)null);
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result.getRoot().withReplacement(contributor, bound));
} else {
ConfigDataLocationResolverContext locationResolverContext = new ConfigDataEnvironmentContributors.ContributorConfigDataLocationResolverContext(result, contributor, activationContext);
ConfigDataLoaderContext loaderContext = new ConfigDataEnvironmentContributors.ContributorDataLoaderContext(this);
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext, locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> {
return this.getImportedMessage(imported.keySet());
}));
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase, this.asContributors(imported));
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result.getRoot().withReplacement(contributor, contributorAndChildren));
++processed;
}
}
}
ConfigDataEnvironmentContributor这个类是一个树状结构的对象,在以上代码段中,将会遍历这棵树:
this.getNextToProcess(result, activationContext, importPhase);
在这棵树的root位置是一个INITIAL_IMPORT的ConfigDataEnvironmentContributor对象,这个对象会包含默认配置文件的文件位置,通过 List<ConfigDataLocation> imports = contributor.getImports()来获取ConfigDataLocation的列表。接下来通过传进来的ConfigDataImporter对象去解析并加载配置文件中包含的信息
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext, locationResolverContext, loaderContext, imports),并保存进一个Map当中,我们看看resolveAndLoad函数是怎样的:
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext, ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, List<ConfigDataLocation> locations) {
try {
Profiles profiles = activationContext != null ? activationContext.getProfiles() : null;
List<ConfigDataResolutionResult> resolved = this.resolve(locationResolverContext, profiles, locations);
return this.load(loaderContext, resolved);
} catch (IOException var7) {
throw new IllegalStateException("IO error on loading imports from " + locations, var7);
}
}
通过resolve方法根据locations获得配置文件列表并通过load方法加载配置文件,返回的map中的value是ConfigData对象,我们看看ConfigData对象的定义是啥:

PropertySource类型 太熟悉了是不是?经过这一系列的操作之后,我们算是获取到了默认情况下配置文件中包含的键值对了, processWithoutProfiles 和processWithProfiles函数都会调用核心代码withProcessedImports,所以细节就不再叙说了,值得注意的一点是,每一个ConfigDataLocation实例都代表了配置文件夹位置,那么默认情况下是在哪里初始化的呢?请看以下代码:
static {
List<ConfigDataLocation> locations = new ArrayList();
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = (ConfigDataLocation[])locations.toArray(new ConfigDataLocation[0]);
EMPTY_LOCATIONS = new ConfigDataLocation[0];
CONFIG_DATA_LOCATION_ARRAY = Bindable.of(ConfigDataLocation[].class);
STRING_LIST = Bindable.listOf(String.class);
ALLOW_INACTIVE_BINDING = new BinderOption[0];
DENY_INACTIVE_BINDING = new BinderOption[]{BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE};
}
这段静态代码段存在于ConfigDataEnvironment类当中,那么我们是什么时候初始化这个类的呢?还记不记得getConfigDataEnvironment函数?至此读配置文件的过程就结束了,那么接下来就是要把之前的这个map放到environment里面了,所以在processAndApply函数中的最后一段代码就是来做这个事情的:
this.applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(), importer.getOptionalLocations()),我们最后来看看是如何加载进环境当中的:
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors, ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations, Set<ConfigDataLocation> optionalLocations) {
this.checkForInvalidProperties(contributors);
this.checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
MutablePropertySources propertySources = this.environment.getPropertySources();
this.applyContributor(contributors, activationContext, propertySources);
DefaultPropertiesPropertySource.moveToEnd(propertySources);
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
this.environmentUpdateListener.onSetProfiles(profiles);
}
很好理解是不是,都是一些set函数,那现在我们这个ApplicationContext的环境成功读取了配置文件的信息。