在前文对Spring Boot中各类关于配置相关的内容都做出了相关分析,本节将对Spring Boot中关于配置加载的整体过程进行分析。本节主要讨论application.yml(yaml)和application.properties的加载流程。当Spring Boot项目启动时,第一个入口是org.springframework.boot.SpringApplication#run(java.lang.Class<?>,java.lang.String...)方法,再继续向下深入会进入org.springframework.boot.SpringApplication#prepareEnvironment方法,该方法就是核心入口方法。在prepareEnvironment方法中会执行如下代码:
listeners.environmentPrepared(bootstrapContext,environment)
在这段代码中会触发环境准备事件(ApplicationEnvironmentPreparedEvent),实际调用代码如下:
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) { this.initialMulticaster.multicastEvent( new ApplicationEnvironmentPreparedEvent(bootstrapContext, this. application, this.args, environment)); }
在上述代码中发布了ApplicationEnvironmentPreparedEvent事件,在Spring中处理事件还需要对应的事件处理器,关于事件处理器的寻找会通过getApplicationListeners(event,type)代码进行,getApplicationListeners方法执行结果如图5-5所示。
图5-5 getApplicationListeners方法执行结果
在图5-5中可以发现,ApplicationEnvironmentPreparedEvent事件对应的事件处理器有6个:
(1)EnvironmentPostProcessorApplicationListener;
(2)AnsiOutputApplicationListener;
(3)LoggingApplicationListener;
(4)BackgroundPreinitializer;
(5)DelegatingApplicationListener;
(6)FileEncodingApplicationListener。
在上述6个事件处理器中,主要关注的是第(1)个事件处理器,关于事件的处理代码如下:
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { // 应用程序环境准备事件 onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { // 应用程序准备事件 onApplicationPreparedEvent((ApplicationPreparedEvent) event); } if (event instanceof ApplicationFailedEvent) { // 应用程序失败事件 onApplicationFailedEvent((ApplicationFailedEvent) event); } }
此时的事件类型是ApplicationEnvironmentPreparedEvent,会调用onApplicationEnvironment-PreparedEvent方法,在onApplicationEnvironmentPreparedEvent方法中会寻找EnvironmentPost-Processor集合并且遍历所有元素调用postProcessEnvironment方法。在整个处理过程中主要对ConfigDataEnvironmentPostProcessor进行关注,在该对象的处理过程中会进行如下方法调度:
getConfigDataEnvironment(environment, resourceLoader, additionalProfiles). processAndApply
跟随上述方法继续向下追踪,会追踪到resolveAndLoad方法(org.springframework.boot.context.config.ConfigDataImporter#resolveAndLoad),具体方法如下:
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext, ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext, List<ConfigDataLocation> locations) { try { // 获取 profile Profiles profiles = (activationContext != null) ? activationContext. getProfiles() :null; // 解析 List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations); // 加载 return load(loaderContext, resolved); } catch (IOException ex) { throw new IllegalStateException("IO error on loading imports from " + locations, ex); } }
在这段代码中需要关注resolved变量,resolved变量信息如图5-6所示。
图5-6 resolved变量信息
通过图5-6可以发现配置文件已经被解析成功,并且资源对象成功获取。在完成资源对象后需要进行load操作,进入load方法(org.springframework.boot.context.config.Config-DataImporter#load)。在load方法中主要关注的是下面的代码:
try { // 实际加载配置数据 ConfigData loaded = this.loaders.load(loaderContext, resource); // 如果数据集不为空,则加入各个数据容器中 if (loaded != null) { this.loaded.add(resource); this.loadedLocations.add(location); result.put(candidate, loaded); } } catch (ConfigDataNotFoundException ex) { handle(ex, location, resource); }
在上述代码中会通过ConfigData loaded = this.loaders.load(loaderContext,resource)方法获取配置文件中的数据。本例中loaded数据信息如图5-7所示。
图5-7 loaded数据信息
此时数据已经从配置文件中获取,下面需要关注如何放入环境对象中。接下来解析数据的过程是在org.springframework.boot.context.config.ConfigDataEnvironmentContributors#withProcessedImports方法中进行的,在withProcessedImports方法中具体解析代码如下:
// 解析数据 Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,locationResolverContext, loaderContext, imports); this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet()))); // 创建当前贡献者和它的子贡献者,子贡献者是imported中的数据 ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase, asContributors(imported)); // 设置result数据值 result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext, result.getRoot().withReplacement(contributor, contributorAndChildren)); // 处理标记+1 processed++;
上述代码中关于配置文件解析的结果存储在imported变量中,imported数据信息如图5-8所示。
图5-8 imported数据信息
得到imported对象后还需要将其转换成ConfigDataEnvironmentContributor,此时数据结构会发生变化,主要是将数据平铺到各个字段中处理,平铺后的数据信息如图5-9所示。
在转换过程中imported数据会放置到ConfigDataEnvironmentContributor变量中。最后得到contributorAndChildren对象后还需要再进行一次转换,转换成ConfigDataEnvironmentContributors信息,ConfigDataEnvironmentContributors数据信息如图5-10所示。
图5-9 平铺后的数据信息
图5-10 ConfigDataEnvironmentContributors数据信息
在得到上述对象后还会再做一些转换,具体分析在这里就不做展开了。得到环境配置提供者后回到processAndApply(org.springframework.boot.context.config.ConfigDataEnvironment#processAndApply)方法,在processAndApply方法最后会进行applyToEnvironment方法的调度,当该方法调度结束后application配置文件中的数据就会被加入环境对象中,环境对象中的配置信息如图5-11所示。
图5-11 环境对象中的配置信息