目錄:Springboot源碼學習目錄
上文:04、SpringBoot 啟動 准備運行環境(prepareEnvironment)流程(一)
前言:上文中有一個最重要的點,就是發送准備完成事件,這一步里,就有對我們平時用到的 application.properties/application.yaml的配置文件解析,我們再本篇文章中重度講解
一、debug進入發送環境已准備事件
listeners.environmentPrepared(bootstrapContext, environment);
在SpringApplicationRunListeners
中,會調用所有的SpringApplicationRunListener
,其實在只有一個實現類EventPublishingRunListener
// 首先進入這個方法
void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
doWithListeners("spring.boot.application.environment-prepared",
(listener) -> listener.environmentPrepared(bootstrapContext, environment));
}
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction) {
doWithListeners(stepName, listenerAction, null);
}
// 最終進入的方法
private void doWithListeners(String stepName, Consumer<SpringApplicationRunListener> listenerAction,
Consumer<StartupStep> stepAction) {
StartupStep step = this.applicationStartup.start(stepName);
// 遍歷所有的SpringApplicationRunListener,執行environmentPrepared方法
this.listeners.forEach(listenerAction);
if (stepAction != null) {
stepAction.accept(step);
}
step.end();
}
因為SpringApplicationRunListener
只有一個實現EventPublishingRunListener
,我們就看這一個就可以了
需要注意的是這個Spring應用運行監聽器在實例化的過程中還初始化一個廣播器,SimpleApplicationEventMulticaster
,
后續就是用這個廣播器向所有的ApplicationListener
發送事件
public void environmentPrepared(ConfigurableBootstrapContext bootstrapContext, ConfigurableEnvironment environment) {
// 調用廣播器,廣播事件
this.initialMulticaster.multicastEvent(
new ApplicationEnvironmentPreparedEvent(bootstrapContext, this.application, this.args, environment));
}
debug進入廣播器
public void multicastEvent(ApplicationEvent event) {
// 解析事件類型,繼續調用廣播事件重載方法
multicastEvent(event, resolveDefaultEventType(event));
}
``
debug進入廣播事件重載方法
```java
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 獲取一個線程池,如果廣播器中有,則事件發的發送都是異步執行,當前這個廣播器中沒有線程池,所以都是同步執行
Executor executor = getTaskExecutor();
// 獲取到所有ApplicationListener,遍歷調用,下面有截圖獲取到的所有監聽器
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
// 因為executor為null,肯定走的這里
invokeListener(listener, event);
}
}
}
上面圖片里是獲取到的監聽器,一共有6個,而最重要的是EnvironmentPostProcessorApplicationListener
,而我們后面只需要看這一個監聽器就可以了
debug進入invokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
// 這里 errorHandler 也是null
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
// 走到這個位置,真正執行的地方
doInvokeListener(listener, event);
}
}
debug進入doInvokeListener
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 執行事件回調方法
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
//省略不重要邏輯
}
}
前面我們也說的,一共獲取到6個監聽器,我們現在只需看EnvironmentPostProcessorApplicationListener
這一個監聽器就可以
debug進入EnvironmentPostProcessorApplicationListener
的onApplicationEvent方法
public void onApplicationEvent(ApplicationEvent event) {
// 前面發送的就是ApplicationEnvironmentPreparedEvent事件,進入這里
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent((ApplicationPreparedEvent) event);
}
if (event instanceof ApplicationFailedEvent) {
onApplicationFailedEvent((ApplicationFailedEvent) event);
}
}
debug進入onApplicationEnvironmentPreparedEvent
,這里面會獲取環境后置處理器,也可以叫環境增強器,EnvironmentPostProcessor
然后遍歷執行
private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {
ConfigurableEnvironment environment = event.getEnvironment();
SpringApplication application = event.getSpringApplication();
// 獲取環境后置處理器,遍歷執行,其實我們真正關注的,是對配置文件進行解析的那個,也就是ConfigDataEnvironmentPostProcessor
// 下面圖片中會展示獲取到的所有的后置環境增強器
for (EnvironmentPostProcessor postProcessor : getEnvironmentPostProcessors(event.getBootstrapContext())) {
postProcessor.postProcessEnvironment(environment, application);
}
}
上面圖片中所有的EnvironmentPostProcessor
中我們只需關系ConfigDataEnvironmentPostProcessor
,后面也只需要debug這個就可以
在springboot2.4之前的配置文件解析是ConfigFileApplicationListener
這個類
但是2.4后springboot對配置文件的用法做了一次修改,2.4之后就是ConfigDataEnvironmentPostProcessor
這個類對配置文件進行解析
我們debug找到ConfigDataEnvironmentPostProcessor
,進入postProcessEnvironment
方法
public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
// 進入重載方法
postProcessEnvironment(environment, application.getResourceLoader(), application.getAdditionalProfiles());
}
繼續debug,里面分為兩大步
1、獲取ConfigDataEnvironment
對象
2、執行ConfigDataEnvironment
對象中的processAndApply
方法
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) {
// 省略不重要邏輯
}
}
二、獲取ConfigDataEnvironment
對象
debug進入getConfigDataEnvironment
,我們看到創建了一個ConfigDataEnvironment,而我們就是要看他的實例化過程
ConfigDataEnvironment getConfigDataEnvironment(ConfigurableEnvironment environment, ResourceLoader resourceLoader,
Collection<String> additionalProfiles) {
return new ConfigDataEnvironment(this.logFactory, this.bootstrapContext, environment, resourceLoader,
additionalProfiles, this.environmentUpdateListener);
}
首先是初始化常量,這里我只列重要的,后面會出現的,為了方便講解,我會調整順序順序,源碼里面的常量順序不一樣
// 默認的配置文件所在目錄路徑,下面的靜態代碼塊是向其中加入的配置文件目錄路徑
// 注意,只會讀取配置路徑目錄下面的以 application 開頭以 .properties /.yaml /.yml 結尾的配置文件
static final ConfigDataLocation[] DEFAULT_SEARCH_LOCATIONS;
// 加入的默認路徑,
// 需要注意的是,下面加入的目錄路徑前面都有,optional關鍵字,
// 如果路徑前加optional,則說明該配置可有可以無,不加則說明必須有,沒有會報錯
static {
List<ConfigDataLocation> locations = new ArrayList<>();
// 類路徑下,或者類路徑下的config目錄,就是我們常見的resource下
locations.add(ConfigDataLocation.of("optional:classpath:/;optional:classpath:/config/"));
// 當前項目根目錄下,或者當前項目跟目錄下的,config目錄下
locations.add(ConfigDataLocation.of("optional:file:./;optional:file:./config/;optional:file:./config/*/"));
DEFAULT_SEARCH_LOCATIONS = locations.toArray(new ConfigDataLocation[0]);
}
// 覆蓋默認位置的配置文件,也就是使默認的配置路徑下的配置文件失效,有兩種用法
// 1、配置一個目錄,會自動讀取目錄下的 以 application 開頭以 .properties /.yaml /.yml 結尾的配置文件,
// 並且如果目錄下不存在滿足條件的配置文件,配置路徑前面加不加optional關鍵字都不會報錯,會正常啟動,這種情況會當做沒有配置文件啟動,哪怕默認位置也有配置文件
//
// 2、直接配置一個配置文件,配置文件可以用任意名稱作為配置文件的名字,不用必須是 appplication開頭
// 但是直接配置的配置文件,如果不存在,會報錯,需要加上optional關鍵字,才能正常啟動,這種情況會當做沒有配置文件啟動,哪怕默認位置也有配置文件
static final String LOCATION_PROPERTY = "spring.config.location";
// 使用指定位置目錄下的application.properties配置文件,默認位置的配置文件也生效,也是有兩種用法
// 1、配置一個目錄,會自動讀取目錄下的 以 application 開頭以 .properties /.yaml /.yml 結尾的配置文件,
// 並且如果目錄下不存在滿足條件的配置文件,配置路徑前面加不加optional關鍵字都不會報錯,會正常啟動,這種情況只有默認位置的配置文件生效(前提默認位置也有配置文件,否則也是無配置文件啟動)
//
// 2、直接配置一個配置文件,配置文件可以用任意名稱作為配置文件的名字,不用必須是 appplication開頭
// 但是直接配置的配置文件,如果不存在,會報錯,需要加上optional關鍵字,才能正常啟動,這種情況只有默認位置的配置文件生效(前提默認位置也有配置文件,否則也是無配置文件啟動)
static final String ADDITIONAL_LOCATION_PROPERTY = "spring.config.additional-location";
// 導入指定配置文件,和spring.config.additional-location的用法一模一樣,可能有不一樣的地方,我沒找到,有大佬看到可以支出
// 不過一般spring.config.import多配置在配置文件中,而spring.config.additional-location多用於命令行
static final String IMPORT_PROPERTY = "spring.config.import";
// 是一個全局的配置,如果指定的配置文件不存在如何處理,該配置可以配置為 fail 或者 ignore,默認為fail
// fail 找不到對應的配置文件則報錯,中斷啟動
// ignore 找不到對應的配置文件忽略,繼續啟動
static final String ON_NOT_FOUND_PROPERTY = "spring.config.on-not-found";
// 空路徑,后續解析各個配置下的配置文件,如果不存在配置文件,則返回這個空路徑
private static final ConfigDataLocation[] EMPTY_LOCATIONS = new ConfigDataLocation[0];
// 配置文件位置數據模板,后面將所有的配置文件的位置都會根據這個常量解析成ConfigDataLocation類型
private static final Bindable<ConfigDataLocation[]> CONFIG_DATA_LOCATION_ARRAY = Bindable.of(ConfigDataLocation[].class);
private static final Bindable<List<String>> STRING_LIST = Bindable.listOf(String.class);
private static final BinderOption[] ALLOW_INACTIVE_BINDING = {};
private static final BinderOption[] DENY_INACTIVE_BINDING = { BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE };
然后是執行構造方法里面的初始化操作,到這我們先提前注意幾個點,防止后面混亂
配置文件路徑解析器集合類ConfigDataLocationResolvers
,和配置文件解析器類ConfigDataLocationResolver
不是同一種類型,ConfigDataLocationResolvers
里面有個集合,存放了所有的ConfigDataLocationResolver
同理還有配置文件加載器集合類ConfigDataLoaders
和配置文件加載器類ConfigDataLoader
,配置數據環境貢獻者集合類ConfigDataEnvironmentContributors
和配置數據環境貢獻者類ConfigDataEnvironmentContributor
配置數據環境貢獻者集合類和配置數據環境貢獻者類和其他兩個又有點區別,配置數據環境貢獻者集合對象中放了一個Root配置數據環境貢獻者對象,
Root配置數據環境貢獻者對象里面存了一個map,key為ImportPhase.BEFORE_PROFILE_ACTIVATION
,value是一個list集合,存放了所有的配置數據環境貢獻者ConfigDataEnvironmentContributor
,這些我們再后面會詳解結束
ConfigDataEnvironment(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
ConfigurableEnvironment environment, ResourceLoader resourceLoader, Collection<String> additionalProfiles,
ConfigDataEnvironmentUpdateListener environmentUpdateListener) {
// 將環境對象(environment)轉換成Binder類型,便於配置的操作
Binder binder = Binder.get(environment);
// 2.4版本后配置文件進行了大變革,這里和舊的配置有關,此處用不到,不用關系
UseLegacyConfigProcessingException.throwIfRequested(binder);
this.logFactory = logFactory;
this.logger = logFactory.getLog(getClass());
// 全局配置,沒有找到對應的配置文件該如何操作,默認是失敗,如果spring.config.on-not-found的配置文ignore,則會忽略,無論配置文件前面是否有添加optional
this.notFoundAction = binder.bind(ON_NOT_FOUND_PROPERTY, ConfigDataNotFoundAction.class)
.orElse(ConfigDataNotFoundAction.FAIL);
this.bootstrapContext = bootstrapContext;
this.environment = environment;
// 初始化配置文件路徑解析器集合對象,見2.1
this.resolvers = createConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
// 附加配置文件,為空
this.additionalProfiles = additionalProfiles;
// 環境更新監聽器,傳入的environmentUpdateListener為空,初始化一個空的監聽器
this.environmentUpdateListener = (environmentUpdateListener != null) ? environmentUpdateListener
: ConfigDataEnvironmentUpdateListener.NONE;
// 初始化配置文件加載器集合對象 見2.2
this.loaders = new ConfigDataLoaders(logFactory, bootstrapContext);
// 初始化配置文件貢獻者集合對象 見2.3,最重要
this.contributors = createContributors(binder);
}
2.1、初始化配置文件路徑解析器集合對象
protected ConfigDataLocationResolvers createConfigDataLocationResolvers(DeferredLogFactory logFactory,
ConfigurableBootstrapContext bootstrapContext, Binder binder, ResourceLoader resourceLoader) {
// 創建一個ConfigDataLocationResolvers對象
return new ConfigDataLocationResolvers(logFactory, bootstrapContext, binder, resourceLoader);
}
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader) {
// 調用重載構造方法,從spring.factory中獲取ConfigDataLocationResolver類型的所有實現
this(logFactory, bootstrapContext, binder, resourceLoader,
SpringFactoriesLoader.loadFactoryNames(ConfigDataLocationResolver.class, ConfigDataLocationResolver.class.getClassLoader()));
}
上面從spring.factory中獲取ConfigDataLocationResolver
類型的所有實現有兩個
names里面是 StandardConfigDataLocationResolver
,ConfigTreeConfigDataLocationResolver
繼續debug
ConfigDataLocationResolvers(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
Binder binder, ResourceLoader resourceLoader, List<String> names) {
Instantiator<ConfigDataLocationResolver<?>> instantiator = new Instantiator<>(ConfigDataLocationResolver.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(DeferredLogFactory.class, logFactory);
availableParameters.add(Binder.class, binder);
availableParameters.add(ResourceLoader.class, resourceLoader);
availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
// 將names中的類進行實例化,並且調reorder方法進行一下轉換
this.resolvers = reorder(instantiator.instantiate(names));
}
就是將ConfigDataLocationResolver
進行一次轉換,然后返回一個不可變集合
private List<ConfigDataLocationResolver<?>> reorder(List<ConfigDataLocationResolver<?>> resolvers) {
List<ConfigDataLocationResolver<?>> reordered = new ArrayList<>(resolvers.size());
StandardConfigDataLocationResolver resourceResolver = null;
for (ConfigDataLocationResolver<?> resolver : resolvers) {
if (resolver instanceof StandardConfigDataLocationResolver) {
resourceResolver = (StandardConfigDataLocationResolver) resolver;
}
else {
reordered.add(resolver);
}
}
if (resourceResolver != null) {
reordered.add(resourceResolver);
}
return Collections.unmodifiableList(reordered);
}
2.2、初始化配置文件加載器集合對象
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext) {
/ 調用重載構造方法,從spring.factory中獲取ConfigDataLoader類型的所有實現
this(logFactory, bootstrapContext, SpringFactoriesLoader.loadFactoryNames(ConfigDataLoader.class, null));
}
上面從spring.factory中獲取ConfigDataLoader
類型的所有實現有兩個
names里面是 StandardConfigDataLoader
,ConfigTreeConfigDataLoader
繼續debug
ConfigDataLoaders(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<String> names) {
this.logger = logFactory.getLog(getClass());
Instantiator<ConfigDataLoader<?>> instantiator = new Instantiator<>(ConfigDataLoader.class,
(availableParameters) -> {
availableParameters.add(Log.class, logFactory::getLog);
availableParameters.add(DeferredLogFactory.class, logFactory);
availableParameters.add(ConfigurableBootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapContext.class, bootstrapContext);
availableParameters.add(BootstrapRegistry.class, bootstrapContext);
});
// 將names中的類進行實例化
this.loaders = instantiator.instantiate(names);
// 在根據loader獲取對應的資源類型
this.resourceTypes = getResourceTypes(this.loaders);
}
2.3、初始配置數據環境貢獻者集合對象
在講這個配置數據環境貢獻者ConfigDataEnvironmentContributor
之前我們先說明白這個是什么
首先應用運行的時候,所有的配置都存在環境對象中(environment),而環境對象中在前面文章中我們也出現過,會存在PropertySource
集合,而后面出現的配置文件,最終也會解析成PropertySource
對象,存入環境中的PropertySource
集合
在初始化貢獻者之前我們再看看現在環境中PropertySource
集合有哪些PropertySource
可以看到上圖中有7條PropertySource
,而這7條屬性源都會解析成ConfigDataEnvironmentContributor
,用來幫助后續配置文件的解析,因為解析配置文件的時候,會用到之前已經存在的配置
ConfigDataEnvironmentContributor
還區分類型,在debug的過程中就能看見每一個貢獻者的角色,有以下幾種
- ROOT:根貢獻者,本身提供任何配置,但是有的貢獻者都在root貢獻者里存儲
- EXISTING:已存在貢獻者,在初始化配置文件之前,也就是創建配置數據環境貢獻者集合對象之前就存在的配置,比如上圖中已經存在的屬性源對象
PropertySource
,解析成的貢獻者 - INITIAL_IMPORT:初始導入貢獻者,在配置數據環境貢獻者集合對象創建完成后,由spring.config.location,spring.config.additional-location,spring.config.import,指定的路徑或者默認的配置路徑,此貢獻者用於解析出其他貢獻者,本身不提供任何配置
- UNBOUND_IMPORT:未導入完成貢獻者,已經解析的貢獻者,但是本身可能會解析出更多貢獻者
- BOUND_IMPORT:導入完成貢獻者,解析完成的貢獻者,里面已經有屬性源
- EMPTY_LOCATION:空的貢獻者
private ConfigDataEnvironmentContributors createContributors(Binder binder) {
this.logger.trace("Building config data environment contributors");
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()));
// 將環境中已有的屬性源PropertySource解析為已存在貢獻者,見2.3.1
contributors.add(ConfigDataEnvironmentContributor.ofExisting(propertySource));
}
}
// 將啟動參數中的spring.config.location,spring.config.additional-location,spring.config.import
// 等配置指定的路徑解析成初始導入貢獻者 見2.3.2
contributors.addAll(getInitialImportContributors(binder));
if (defaultPropertySource != null) {
this.logger.trace("Creating wrapped config data contributor for default property source");
contributors.add(ConfigDataEnvironmentContributor.ofExisting(defaultPropertySource));
}
// 將所有的貢獻者傳入,創建一個貢獻者集合對象,見2.3.3
return createContributors(contributors);
}
2.3.1、解析已存在貢獻者
由已經存在的PropertySource
,解析成的貢獻者,未其他貢獻者解析提供初始配置
static ConfigDataEnvironmentContributor ofExisting(PropertySource<?> propertySource) {
return new ConfigDataEnvironmentContributor(Kind.EXISTING, null, null, false, propertySource,
ConfigurationPropertySource.from(propertySource), null, null, null);
}
2.3.2、解析初始導入貢獻者
將啟動參數中的spring.config.location,spring.config.additional-location,spring.config.import配置的配置文件路徑解析為初始導入貢獻者
// bindLocations方法解析見 2.3.2.1
// addInitialImportContributors方法解析見 2.3.2.2
private List<ConfigDataEnvironmentContributor> getInitialImportContributors(Binder binder) {
// 導入貢獻者集合,收集后面由啟動參數創建的貢獻者
List<ConfigDataEnvironmentContributor> initialContributors = new ArrayList<>();
// IMPORT_PROPERTY就是spring.config.import,EMPTY_LOCATIONS就是一個空的路徑對象
// 從啟動參數里獲取spring.config.import配置,如果有,則創建一個配置路徑對象ConfigDataLocation,沒有就使用空的配置路徑對象
addInitialImportContributors(initialContributors,
bindLocations(binder, IMPORT_PROPERTY, EMPTY_LOCATIONS));
// ADDITIONAL_LOCATION_PROPERTY就是spring.config.additional,EMPTY_LOCATIONS就是一個空的路徑對象
// 從啟動參數里獲取spring.config.additional配置,如果有,則創建一個配置路徑對象ConfigDataLocation,沒有就使用空的配置路徑對象
addInitialImportContributors(initialContributors,
bindLocations(binder, ADDITIONAL_LOCATION_PROPERTY, EMPTY_LOCATIONS));
// LOCATION_PROPERTY就是spring.config.location,
// DEFAULT_SEARCH_LOCATIONS里就是默認的路徑對象,里面有兩個路徑對象,optional:classpath:/;optional:classpath:/config/和optional:file:./;optional:file:./config/;optional:file:./config/*/
// 從啟動參數里獲取spring.config.location配置,如果有,則創建一個配置路徑對象ConfigDataLocation,沒有就使用默認的配置路徑對象
addInitialImportContributors(initialContributors,
bindLocations(binder, LOCATION_PROPERTY, DEFAULT_SEARCH_LOCATIONS));
return initialContributors;
}
2.3.2.1、構建路徑對象
// binder 由環境對象解析而來,里面有所有的系統環境變量,jvm環境變量,啟動參數變量
// propertyName 要從環境中獲取的key
// other 如果獲取不到,則返回的默認對象
private ConfigDataLocation[] bindLocations(Binder binder, String propertyName, ConfigDataLocation[] other) {
return binder.bind(propertyName, CONFIG_DATA_LOCATION_ARRAY).orElse(other);
}
2.3.2.2、創建初始導入貢獻者對象
// initialContributors 收集貢獻者
// locations 要解析為貢獻者的路徑對象
private void addInitialImportContributors(List<ConfigDataEnvironmentContributor> initialContributors,
ConfigDataLocation[] locations) {
for (int i = locations.length - 1; i >= 0; i--) {
initialContributors.add(createInitialImportContributor(locations[i]));
}
}
// 將傳入的配置數據路徑對象轉為 初始導入貢獻者
private ConfigDataEnvironmentContributor createInitialImportContributor(ConfigDataLocation location) {
this.logger.trace(LogMessage.format("Adding initial config data import from location '%s'", location));
return ConfigDataEnvironmentContributor.ofInitialImport(location);
}
2.3.3 創建配置數據環境貢獻者集合對象
protected ConfigDataEnvironmentContributors createContributors(
List<ConfigDataEnvironmentContributor> contributors) {
// 調用重載構造方法
return new ConfigDataEnvironmentContributors(this.logFactory, this.bootstrapContext, contributors);
}
ConfigDataEnvironmentContributors(DeferredLogFactory logFactory, ConfigurableBootstrapContext bootstrapContext,
List<ConfigDataEnvironmentContributor> contributors) {
this.logger = logFactory.getLog(getClass());
this.bootstrapContext = bootstrapContext;
// 創建一個根貢獻者,根貢獻者包含所有的貢獻者
this.root = ConfigDataEnvironmentContributor.of(contributors);
}
static ConfigDataEnvironmentContributor of(List<ConfigDataEnvironmentContributor> contributors) {
// 以ImportPhase.BEFORE_PROFILE_ACTIVATION為key,將之前獲取到的,已存在貢獻者,初始導入貢獻者 的集合作為value,
Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children = new LinkedHashMap<>();
children.put(ImportPhase.BEFORE_PROFILE_ACTIVATION, Collections.unmodifiableList(contributors));
// 創建一個根貢獻者
return new ConfigDataEnvironmentContributor(Kind.ROOT, null, null, false, null, null, null, null, children);
}
創建一個根貢獻者,根貢獻者初始化只有自己的角色,以及children屬性
ConfigDataEnvironmentContributor(Kind kind, ConfigDataLocation location, ConfigDataResource resource,
boolean fromProfileSpecificImport, PropertySource<?> propertySource,
ConfigurationPropertySource configurationPropertySource, ConfigDataProperties properties,
ConfigData.Options configDataOptions, Map<ImportPhase, List<ConfigDataEnvironmentContributor>> children) {
this.kind = kind;
this.location = location;
this.resource = resource;
this.fromProfileSpecificImport = fromProfileSpecificImport;
this.properties = properties;
this.propertySource = propertySource;
this.configurationPropertySource = configurationPropertySource;
this.configDataOptions = (configDataOptions != null) ? configDataOptions : ConfigData.Options.NONE;
this.children = (children != null) ? children : Collections.emptyMap();
}
三、執行ConfigDataEnvironment
對象中的procesAndApply
方法
之前我在讀源碼時自己是大概看明白了,過程很復雜,但是一直不知道怎么去解釋,直到我看了這篇文章(SpringBoot外部化配置相關源碼API剖析和擴展)[https://www.codenong.com/jsb307d1a5c1bb/]自己覺得比較好,拾人牙慧,借鑒一下
procesAndApply
方法就是把前面的貢獻者ConfigDataEnvironmentContributor
中配置,轉換為 PropertySource
,並且應用到環境中的過程
整個解析分為三個階段
- 無Profiles無CloudPlatform階段
- CloudPlatform解析階段,根據環境參數spring.main.cloud-platform或者環境變量參數來自動探測雲計算廠商環境
- Profiles解析階段,也就是解析配置了spring.profiles.active,spring.profiles.group 等配置的階段, 將applicaton-{profile}.properties形式的配置文件中的配置解析出來
每個階段的解析步驟也分為三步 - 將配置數據位置信息
ConfigDataLocation
解析為配置數據資源返回結果ConfigDataResolutionResult
,ConfigDataResolutionResult
包含配置數據位置信息ConfigDataLocation
和配置數據資源ConfigDataResource
- 加載配置資源返回結果
ConfigDataResolutionResult
中的ConfigDataResource
,解析成ConfigData
,ConfigData
中包含一個PropertySource
- 再將新解析出的
PropertySource
轉變為貢獻者,然后替換原有的貢獻者
當三個階段都解析完成后,就會將貢獻者中的PropertySource
,加入到環境中
說完上面這些理論性的東西,我們再看時解析之前,先看看我的配置文件,以及啟動參數
啟動參數spring.profiles.active=test
,主配置文件中配置了spring.profiles.group.test=testdb,testmq
,所以application-test.properties,application-testdb.properties,application-testmq.properties三個配置文件會激活
啟動參數spring.config.additional-location=file:./app-custom/custom.properties
,所以app-custom目錄下custom.properties會激活
啟動參數spring.config.import=file:./app-import/
,所以app-import目錄下的application.properties會激活,
app-import目錄下的application.properties中配置了spring.config.import=file:./app-import/other.properties
,所以app-import目錄下的other.properties也會激活
開始debug,先看一下此時的root貢獻者
void processAndApply() {
// 創建一個數據導入器,數據導入器專門用於解析貢獻者中的路徑,到具體解析時在看, 實例化過程就不看了
ConfigDataImporter importer = new ConfigDataImporter(this.logFactory, this.notFoundAction, this.resolvers, this.loaders);
registerBootstrapBinder(this.contributors, null, DENY_INACTIVE_BINDING);
// 第一階段,解析初始導入貢獻者,見3.1
ConfigDataEnvironmentContributors contributors = processInitial(this.contributors, importer);
// 創建一個配置環境激活上下文,激活上下文和雲平台以及profiles的關系,此時創建的只推斷雲平台,因為我們是本地debug,沒有雲平台相關配置,其中的cloudPlatform屬性為空
ConfigDataActivationContext activationContext = createActivationContext(contributors.getBinder(null, BinderOption.FAIL_ON_BIND_TO_INACTIVE_SOURCE));
// 第二階段,和雲平台相關配置,由於上面解析cloudPlatform為null,所以貢獻者沒有任何變化,我們也不用看,哪怕有雲配置相關配置,解析步驟也和第一階段一樣,我們只需要看3.1
contributors = processWithoutProfiles(contributors, importer, activationContext);
// 對配置環境激活上下文進行處理,這次從環境中獲取了了profiles相關的配置
activationContext = withProfiles(contributors, activationContext);
// 第三階段,解析啟動參數中的 spring.profiles.active 以及,主配置文件中的 spring.profiles.group,解析流和第三階段一樣,見3.1
contributors = processWithProfiles(contributors, importer, activationContext);
// 所有的貢獻者都被解析出來,並且每個貢獻者的屬性源也被解析出來,該方法就是將貢獻者中的屬性源,添加到環境中,見3.2
applyToEnvironment(contributors, activationContext, importer.getLoadedLocations(),importer.getOptionalLocations());
}
3.1、解析配置,三個階段的入口
三個階段入口大同小異,最終都是調用了,貢獻者集合對象的withProcessedImports
方法
// 第一階段入口,無activationContext,其實就是無雲平台,無profiles
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;
}
// 第二階段入口,如果配置了雲平台,此時就會進行雲平台解析,但是我們這次沒有
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;
}
// 第三階段入口,如果配置了 spring.profile.active和spring.profile.groups 則會進行響應解析,我們上文中介紹到有配置
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;
}
debug進入withProcessedImports
方法
ConfigDataEnvironmentContributors withProcessedImports(ConfigDataImporter importer,
ConfigDataActivationContext activationContext) {
// 第一第二階段解析是值為BEFORE_PROFILE_ACTIVATION,第三階段解析時值為AFTER_PROFILE_ACTIVATION
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) {
// 獲取一個需要處理的貢獻者,需要處理的貢獻者要滿足下面兩個條件中的任意一個條件
// 1.貢獻者的角色為UNBOUND_IMPORT
// 2.children屬性集合中沒有importPhase這個key,並且滿足下面4個條件中任意一個條件
// 2.1、properties為null
// 2.2、properties不為null,但是properties的activate屬性為null
// 2.3、properties不為null,properties的activate屬性也不為null,但是傳入的activationContext為null
// 2.4、properties,properties的activate,傳入的activationContext都不為為null,並且滿足下面兩個條件
// 2.4.1、properties中的onCloudPlatform為null or properties中的onCloudPlatform不為null並且和activationContext中onCloudPlatform相同
// 2.4.2、properties中的onProfile為null or properties中的onProfile不為null並且和activationContext中的profiles匹配
// 第二個條件的含義簡單說就是,貢獻者有properties中指示了有要解析的配置文件路徑,但是children中發現並沒有對應的解析配置,所以就需要解析
ConfigDataEnvironmentContributor contributor = getNextToProcess(result, activationContext, importPhase);
if (contributor == null) {
this.logger.trace(LogMessage.format("Processed imports for of %d contributors", processed));
return result;
}
// 角色為UNBOUND_IMPORT的貢獻者進行解析
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, null, null, null);
// 配置文件中的spring.config.import之類的配置解析
ConfigDataEnvironmentContributor bound = contributor.withBoundProperties(binder);
// 將解析后的貢獻者替換原有貢獻者
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);
// 獲取要解析的配置數據路徑
List<ConfigDataLocation> imports = contributor.getImports();
this.logger.trace(LogMessage.format("Processing imports %s", imports));
// 上文提到的配置數據導入器,就在此處用到,將路徑解析上下文,配置數據加載上下文,要加載的配置數據路徑全部傳入,這也是解析的核心步驟,包含了解析三大步的兩大步,也是接下來的debug的地方
// 解析的結果就是一個map,key為ConfigDataResolutionResult包含了location和resource,value為ConfigData,包含了PropertiesSource
Map<ConfigDataResolutionResult, ConfigData> imported = importer.resolveAndLoad(activationContext,
locationResolverContext, loaderContext, imports);
this.logger.trace(LogMessage.of(() -> getImportedMessage(imported.keySet())));
// 后面的流程就是解析的第三大步
// 創建一個新的貢獻者,這個貢獻者在importPhase階段的子貢獻者就是解析出的數據
ConfigDataEnvironmentContributor contributorAndChildren = contributor.withChildren(importPhase,
asContributors(imported));
// 替換root貢獻者中的當前解析貢獻者,為上面的新的已被解析的貢獻者,
result = new ConfigDataEnvironmentContributors(this.logger, this.bootstrapContext,
result.getRoot().withReplacement(contributor, contributorAndChildren));
processed++;
}
}
debug進入ConfigDataImporter
的resolveAndLoad
方法
Map<ConfigDataResolutionResult, ConfigData> resolveAndLoad(ConfigDataActivationContext activationContext,
ConfigDataLocationResolverContext locationResolverContext, ConfigDataLoaderContext loaderContext,
List<ConfigDataLocation> locations) {
try {
// 獲取profiles,第一階段為空,在第三階段時如果配置了,則有值
Profiles profiles = (activationContext != null) ? activationContext.getProfiles() : null;
// 解析的第一大步,解析位置路徑,見3.1.1
List<ConfigDataResolutionResult> resolved = resolve(locationResolverContext, profiles, locations);
// 解析的第二大步,加載位置路徑中的配置,見3.1.2
return load(loaderContext, resolved);
}
catch (IOException ex) {
throw new IllegalStateException("IO error on loading imports from " + locations, ex);
}
}
3.1.1、解析位置路徑
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, List<ConfigDataLocation> locations) {
// 收集配置數據路徑解析返回
List<ConfigDataResolutionResult> resolved = new ArrayList<>(locations.size());
// 遍歷要解析的配置文件路徑
for (ConfigDataLocation location : locations) {
// 調用重載的解析方法
resolved.addAll(resolve(locationResolverContext, profiles, location));
}
return Collections.unmodifiableList(resolved);
}
繼續debug,發現調用了解析器結合對象的解析方法
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext locationResolverContext,
Profiles profiles, ConfigDataLocation location) {
try {
//ConfigDataImporter中的解析器集合對象
return this.resolvers.resolve(locationResolverContext, location, profiles);
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location, null);
return Collections.emptyList();
}
}
debug進入解析器集合對象ConfigDataLocationResolvers
的resolve
方法
List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolverContext context, ConfigDataLocation location,
Profiles profiles) {
if (location == null) {
return Collections.emptyList();
}
// 遍歷解析器,尋找一個能夠解析當前路徑的解析器,一般我們獲取的都是StandardConfigDataLocationResolver解析器
for (ConfigDataLocationResolver<?> resolver : getResolvers()) {
if (resolver.isResolvable(context, location)) {
// 繼續調用解析器集合對象中的重載方法,這一次傳入了一個解析器
return resolve(resolver, context, location, profiles);
}
}
throw new UnsupportedConfigDataLocationException(location);
}
debug進入重載的解析方法
private List<ConfigDataResolutionResult> resolve(ConfigDataLocationResolver<?> resolver,
ConfigDataLocationResolverContext context, ConfigDataLocation location, Profiles profiles) {
// 解析不帶profile的配置文件,也就是application為名稱的主配置文件,我們一會debug這個方法
List<ConfigDataResolutionResult> resolved = resolve(location, false, () -> resolver.resolve(context, location));
// 如果主配置文件都不存在,就不必解析帶有profile的配置文件了
if (profiles == null) {
return resolved;
}
// 解析帶有profile的配置文件,類似於 applicaiton-{profile}.[extend],解析流和上面一模一樣
List<ConfigDataResolutionResult> profileSpecific = resolve(location, true,
() -> resolver.resolveProfileSpecific(context, location, profiles));
// 將兩個結果合並為一個集合
return merge(resolved, profileSpecific);
}
我們只需要看不帶profile的配置文件解析過程就行,和帶profile的配置文件解析過程一樣,就是多在后面拼接了一個profile
private List<ConfigDataResolutionResult> resolve(ConfigDataLocation location, boolean profileSpecific,
Supplier<List<? extends ConfigDataResource>> resolveAction) {
// 調用上面傳入的一個lambda表達式,看看結果是否為空,為空,創建一個空的返回結果,我們接下來debu這個lambda表達式
List<ConfigDataResource> resources = nonNullList(resolveAction.get());
// 收集結果
List<ConfigDataResolutionResult> resolved = new ArrayList<>(resources.size());
for (ConfigDataResource resource : resources) {
// 將解析結果轉換為ConfigDataResolutionResult,包含了路徑信息,資源信息,以及是否為帶有profile的配置
resolved.add(new ConfigDataResolutionResult(location, resource, profileSpecific));
}
return resolved;
}
可見lambda表達式中再次調用重載,分類兩步
一步將路徑ConfigDataLocation
解析為標准配置數據引用 StandardConfigDataReference
一步將標准配置數據引用對象StandardConfigDataReference
解析為標准數據資源對象StandardConfigDataResource
public List<StandardConfigDataResource> resolve(ConfigDataLocationResolverContext context,
ConfigDataLocation location) throws ConfigDataNotFoundException {
// 有的location中的路徑,其實是包含了多個路徑,比如默認的路徑,所以在這里做一次拆分,分割為多個location
//將路徑`ConfigDataLocation`解析為 `StandardConfigDataReference`見3.1.1.1
//將標准配置數據引用對象`StandardConfigDataReference`解析為標准數據資源對象`StandardConfigDataResource`見3.1.1.2
return resolve(getReferences(context, location.split()));
}
3.1.1.1、將路徑ConfigDataLocation
解析為標准配置數據引用 StandardConfigDataReference
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context, ConfigDataLocation[] configDataLocations) {
// 收集標准配置數據引用集合
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
// 遍歷配置數據路徑
for (ConfigDataLocation configDataLocation : configDataLocations) {
// 將路徑`ConfigDataLocation`解析為標准配置數據引用 `StandardConfigDataReference
references.addAll(getReferences(context, configDataLocation));
}
return references;
}
debug進入上面的getReferences
private Set<StandardConfigDataReference> getReferences(ConfigDataLocationResolverContext context, ConfigDataLocation configDataLocation) {
//獲取配置數據位置中的資源路徑
String resourceLocation = getResourceLocation(context, configDataLocation);
try {
//判斷該位置是否為目錄
if (isDirectory(resourceLocation)) {
//如果是目錄則默認獲取名字為application,擴展為.yaml,.yml,.xml,.properties,的配置文件,我們接下來debug這個方法
//第三階段帶profile的配置文件解析也會調用該方法,不過最后一個參數就不是固定的 NO_PROFILE 常量了,而是傳入的profile
return getReferencesForDirectory(configDataLocation, resourceLocation, NO_PROFILE);
}
// 如果不是目錄,則當做文件進行解析,會檢查擴展是否為.yaml,.yml,.xml,.properties,如果不是則報錯,過程我們也不看了
return getReferencesForFile(configDataLocation, resourceLocation, NO_PROFILE);
}
catch (RuntimeException ex) {
throw new IllegalStateException("Unable to load config data from '" + configDataLocation + "'", ex);
}
}
debug進入上面的getReferencesForDirectory
方法
該方法無profile的解析調用,profile參數為null
有profile的解析調用,就是傳入的profile
private Set<StandardConfigDataReference> getReferencesForDirectory(ConfigDataLocation configDataLocation,
String directory, String profile) {
Set<StandardConfigDataReference> references = new LinkedHashSet<>();
// 遍歷默認的配置文件名字,這里只有一個就是 application
for (String name : this.configNames) {
// 真正解析的位置,以及確定了配置文件的名字,位置,目錄,profile
Deque<StandardConfigDataReference> referencesForName = getReferencesForConfigName(name, configDataLocation, directory, profile);
references.addAll(referencesForName);
}
return references;
}
debug進入getReferencesForConfigName
方法
private Deque<StandardConfigDataReference> getReferencesForConfigName(String name, ConfigDataLocation configDataLocation, String directory, String profile) {
Deque<StandardConfigDataReference> references = new ArrayDeque<>();
// 遍歷屬性源加載器,屬性源加載器是在路徑解析器創建是,構造方法中從spring.factory中讀取到並實例化的,有兩個
// PropertiesPropertySourceLoader,解析擴展名為 .properties/.xml的配置文件
// YamlPropertySourceLoader,解析擴展名為 .yaml/.yml 的配置文件
for (PropertySourceLoader propertySourceLoader : this.propertySourceLoaders) {
// 遍歷屬性源加載器的擴展
for (String extension : propertySourceLoader.getFileExtensions()) {
// 創建StandardConfigDataReference,這里面就有配置數據位置,目錄,絕對位置,profile,擴展,以及對應的屬性源解析器
StandardConfigDataReference reference = new StandardConfigDataReference(configDataLocation, directory,
directory + name, profile, extension, propertySourceLoader);
if (!references.contains(reference)) {
references.addFirst(reference);
}
}
}
// 解析完成后,里面一般有4個 標准配置數據引用
return references;
}
3.1.1.2、標准配置數據引用對象StandardConfigDataReference
解析為標准數據資源對象StandardConfigDataResource
在上面一個配置數據位置對象,會解析成4個標准配置數據引用對象,這里就要根據引用對象,解析為資源對象,
private List<StandardConfigDataResource> resolve(Set<StandardConfigDataReference> references) {
List<StandardConfigDataResource> resolved = new ArrayList<>();
// 遍歷標准配置數據引用對象進行解析
for (StandardConfigDataReference reference : references) {
// 接下來要debug的方法
resolved.addAll(resolve(reference));
}
if (resolved.isEmpty()) {
resolved.addAll(resolveEmptyDirectories(references));
}
return resolved;
}
debug上面的要進入的resolve
方法
private List<StandardConfigDataResource> resolve(StandardConfigDataReference reference) {
// 判斷位置路徑中是否有通配符*
if (!this.resourceLoader.isPattern(reference.getResourceLocation())) {
// 一般我們沒有,就要進入這個方法,接下來我們進入這個方法
return resolveNonPattern(reference);
}
// 有通配符就可能有多個文件資源,在這個方法
return resolvePattern(reference);
}
debug進入resolveNonPattern
這個方法
private List<StandardConfigDataResource> resolveNonPattern(StandardConfigDataReference reference) {
// 先通過資源加載器獲取資源
Resource resource = this.resourceLoader.getResource(reference.getResourceLocation());
// 潘德這個資源是否存在,以及是否可跳過
if (!resource.exists() && reference.isSkippable()) {
// 如果資源不存在,且可跳過,就創建一個空的配置數據資源對象
logSkippingResource(reference);
return Collections.emptyList();
}
// 存在就創建一個標准配置數據資源,里面包含 資源 以及 標准配置數據引用對象
return Collections.singletonList(createConfigResourceLocation(reference, resource));
}
3.1.2、加載位置路徑中的配置
在經過配置數據配置解析后的記過為ConfigDataResolutionResult集合,ConfigDataResolutionResult中包含了配置數據位置,以及配置數據資源
將資源中的配置解析出,就在這一步
private Map<ConfigDataResolutionResult, ConfigData> load(ConfigDataLoaderContext loaderContext,
List<ConfigDataResolutionResult> candidates) throws IOException {
Map<ConfigDataResolutionResult, ConfigData> result = new LinkedHashMap<>();
// 遍歷配置數據結果
for (int i = candidates.size() - 1; i >= 0; i--) {
// 獲取配置數據結果
ConfigDataResolutionResult candidate = candidates.get(i);
// 獲取配置數據位置
ConfigDataLocation location = candidate.getLocation();
// 獲取配置數據資源
ConfigDataResource resource = candidate.getResource();
// 檢查這個資源是否可選加入緩存
if (resource.isOptional()) {
this.optionalLocations.add(location);
}
// 檢查這個資源是否已經加載過,加入已經加載過的路徑
if (this.loaded.contains(resource)) {
this.loadedLocations.add(location);
}
else {
try {
// 調用加載器集合對象對資源進行加載,返回的ConfigData對象中有PropertiesSource對象,也是本方法的核心,我們要debug的位置
ConfigData loaded = this.loaders.load(loaderContext, resource);
if (loaded != null) {
// 加載的結果不為null,則存入已加載資源緩存,已加載路徑緩存
this.loaded.add(resource);
this.loadedLocations.add(location);
// 存入返回的記過,key為配置數據結果,value為加載結果
result.put(candidate, loaded);
}
}
catch (ConfigDataNotFoundException ex) {
handle(ex, location, resource);
}
}
}
return Collections.unmodifiableMap(result);
}
debug進入ConfigDataLoaders
的load
方法
<R extends ConfigDataResource> ConfigData load(ConfigDataLoaderContext context, R resource) throws IOException {
// 獲取一個合適 加載器,一般都是StandardConfigDataLoader
ConfigDataLoader<R> loader = getLoader(context, resource);
this.logger.trace(LogMessage.of(() -> "Loading " + resource + " using loader " + loader.getClass().getName()));
// 然后我們用StandardConfigDataLoader加載器加載資源
return loader.load(context, resource);
}
debug進入StandardConfigDataLoader
的load
方法
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();
// 將資源轉為OriginTrackedWritableResource類型
Resource originTrackedResource = OriginTrackedResource.of(resource.getResource(),
Origin.from(reference.getConfigDataLocation()));
// 格式化資源的名字
String name = String.format("Config resource '%s' via location '%s'", resource,
reference.getConfigDataLocation());
// 通過標准配置數據引用里面的 屬性源解析器 來加載資源,返回的結果就是 PropertySource
List<PropertySource<?>> propertySources = reference.getPropertySourceLoader().load(name, originTrackedResource);
PropertySourceOptions options = (resource.getProfile() != null) ? PROFILE_SPECIFIC : NON_PROFILE_SPECIFIC;
// 然后創建一個配置數據對象,傳入屬性源
return new ConfigData(propertySources, options);
}
3.2、將屬性源應用到環境中
private void applyToEnvironment(ConfigDataEnvironmentContributors contributors,
ConfigDataActivationContext activationContext, Set<ConfigDataLocation> loadedLocations,
Set<ConfigDataLocation> optionalLocations) {
// 檢查一些錯誤的配置,比如spring.profile.active,不能出現在非主配置文件中
checkForInvalidProperties(contributors);
// 檢查非默認位置的 配置文件是否都加載過
checkMandatoryLocations(contributors, activationContext, loadedLocations, optionalLocations);
// 獲取環境中的 屬性源集合對象
MutablePropertySources propertySources = this.environment.getPropertySources();
// 將貢獻者中的屬性源都添加到環境的的屬性源集合對象,也是核銷方法
applyContributor(contributors, activationContext, propertySources);
// 將默認的屬性源移動到最后面
DefaultPropertiesPropertySource.moveToEnd(propertySources);
// 獲取profile
Profiles profiles = activationContext.getProfiles();
this.logger.trace(LogMessage.format("Setting default profiles: %s", profiles.getDefault()));
// 設置環境中的默認profiles
this.environment.setDefaultProfiles(StringUtils.toStringArray(profiles.getDefault()));
this.logger.trace(LogMessage.format("Setting active profiles: %s", profiles.getActive()));
// 設置環境中的 激活的profiles
this.environment.setActiveProfiles(StringUtils.toStringArray(profiles.getActive()));
// 發送環境更新事件,其實前面我們寫到過,沒有這個屬性為空
this.environmentUpdateListener.onSetProfiles(profiles);
}
debug 進入 applyContributor
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();
// 將角色為BOUND_IMPORT並且屬性源不為空的貢獻者的屬性源加入 屬性源集合對象中
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());
}
}
}
}
最后我們看看解析前和解析后環境中的PropertiesSource
對比
很明顯,我們之前配置的那些配置文件都已經在里面了