Spring Boot 啟動(二) Environment 加載
Spring Boot 啟動(二) Environment 加載
Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10198698.html)
上一節中講解了 SpringApplication 啟動的整個流程,本節關註第二步 prepareEnvironment,尤其是配置文件的加載。
一、prepareEnvironment 加載流程分析
public ConfigurableApplicationContext run(String... args) { // 1. listeners 用戶監聽容器的運行,默認實現為 EventPublishingRunListener SpringApplicationRunListeners listeners = getRunListeners(args); ApplicationArguments applicationArguments = new DefaultApplicationArguments(args); // 2. 初始化環境變量 environment ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); } private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) { // 1. 根據 webApplicationType 創建相應的 Environment ConfigurableEnvironment environment = getOrCreateEnvironment(); // 2. 配置 Environment,主要有三點:一是 ConversionService;二是數據源,包括命令行參數;三是 Profiles configureEnvironment(environment, applicationArguments.getSourceArgs()); // 3. 激活 environmentPrepared 事件,主要是加載 application.yml 等配置文件 // ConfigFileApplicationListener#ApplicationEnvironmentPreparedEvent listeners.environmentPrepared(environment); bindToSpringApplication(environment); if (!this.isCustomEnvironment) { environment = new EnvironmentConverter(getClassLoader()) .convertEnvironmentIfNecessary(environment, deduceEnvironmentClass()); } // ??? 以後再研究 ConfigurationPropertySources.attach(environment); return environment; }
根據 webApplicationType 類型創建相應的 Environment,分為 StandardEnvironment、StandardServletEnvironment、StandardReactiveWebEnvironment。
configureEnvironment 主要有三點:一是 ConversionService;二是數據源,包括命令行參數;三是 Profiles
激活 environmentPrepared 事件,主要是加載 application.yml 等配置文件
2.1 getOrCreateEnvironment
對於 StandardServletEnvironment 的 servletContextInitParams 和 servletConfigInitParams 兩個 web 的數據源,會先用 StubPropertySource 占位,等初始化 web 容器時再替換。詳見:https://www.cnblogs.com/binarylei/p/10291323.html
2.2 configureEnvironment
protected void configureEnvironment(ConfigurableEnvironment environment, String[] args) { // 1. 設置 ConversionService if (this.addConversionService) { ConversionService conversionService = ApplicationConversionService.getSharedInstance(); environment.setConversionService((ConfigurableConversionService) conversionService); } // 2. 加載 defaultProperties 和 CommandLinePropertySource(main 參數) 信息 configurePropertySources(environment, args); // 3. 設置 environment 的 Profiles(additionalProfiles + spring.profile.active/default) configureProfiles(environment, args); }
configurePropertySources 添加在 Spring Framework 基礎上添加了兩個新的據源,一是自定義的 defaultProperties;二是 CommandLinePropertySource(main 參數)
configureProfiles 在原有的剖面上添加自定義的剖面 additionalProfiles,註意 additionalProfiles 在前,Spring Framework 默認的剖面在後。
2.3 environmentPrepared
listeners.environmentPrepared(environment) 主要是加載配置文件,其中 listeners 是通過 spring.factories 配置的 SpringApplicationRunListener,默認實現是 EventPublishingRunListener。
@Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
environmentPrepared 觸發了 ApplicationEnvironmentPreparedEvent 事件,這個事件是在 spring.factories 配置的監聽器 ConfigFileApplicationListener 處理的。
二、ConfigFileApplicationListener
2.1 ConfigFileApplicationListener 處理流程
public class ConfigFileApplicationListener
implements EnvironmentPostProcessor, SmartApplicationListener, Ordered {
@Override
public void onApplicationEvent(ApplicationEvent event) {
// 1. Environment 加載完成觸發 ApplicationEnvironmentPreparedEvent
if (event instanceof ApplicationEnvironmentPreparedEvent) {
onApplicationEnvironmentPreparedEvent((ApplicationEnvironmentPreparedEvent) event);
}
// 2. ApplicationContext 加載完成觸發 ApplicationPreparedEvent
if (event instanceof ApplicationPreparedEvent) {
onApplicationPreparedEvent(event);
}
}
}
本例中觸發了 ApplicationEnvironmentPreparedEvent 事件。
private void onApplicationEnvironmentPreparedEvent(
ApplicationEnvironmentPreparedEvent event) {
// 1. 委托給 EnvironmentPostProcessor 處理,也是通過 spring.factories 配置
List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();
// 2. ConfigFileApplicationListener 本身也實現了 EnvironmentPostProcessor 接口
postProcessors.add(this);
// 3. spring 都都通過 AnnotationAwareOrderComparator 控制執行順序
AnnotationAwareOrderComparator.sort(postProcessors);
// 4. 執行 EnvironmentPostProcessor
for (EnvironmentPostProcessor postProcessor : postProcessors) {
postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());
}
}
我們重點關註的是 ConfigFileApplicationListener 是如何加載配置文件的,其它的 EnvironmentPostProcessor 暫時忽略。跟蹤 ConfigFileApplicationListener#postProcessEnvironment 方法,最終加載配置文件委托給了其內部類 Loader 完成。
protected void addPropertySources(ConfigurableEnvironment environment,
ResourceLoader resourceLoader) {
// 1. 加載隨機數據源 ${random.int} ${random.long} ${random.uuid}
RandomValuePropertySource.addToEnvironment(environment);
// 2. 加載配置文件
new Loader(environment, resourceLoader).load();
}
三、Loader 加載配置文件
3.1 Spring Boot 默認目錄及配置文件名
Spring Boot 默認的配置文件的目錄及配置文件名稱如下:
// 1. 配置文件默認的目錄,解析時會倒置,所以 Spring Boot 默認 jar 包的配置文件會覆蓋 jar 中的配置文件
private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";
public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";
// 2. 配置文件默認的文件名
private static final String DEFAULT_NAMES = "application";
public static final String CONFIG_NAME_PROPERTY = "spring.config.name";
3.2 profiles 解析配置文件的順序
先了解一起 Spring FrameWork 和 Spring Boot 的 profiles 的概念。
配置 | Spring | 類 | 說明 |
---|---|---|---|
spring.profiles.active | Spring FrameWork | AbstractEnvironment | 激活的剖面 |
spring.profiles.default | Spring FrameWork | AbstractEnvironment | 默認剖面 |
additionalProfiles | Spring Boot | SpringApplication | 自定義激活的剖面 |
spring.profiles.include | Spring Boot | ConfigFileApplicationListener | 自定義激活的剖面 |
在啟動 SpringApplication#prepareEnvironment 時已經激活了 additionalProfiles + Spring FrameWork 剖面,註意剖面的順序。 ConfigFileApplicationListener 引入 spring.profiles.include
private void initializeProfiles() {
// 1. null
this.profiles.add(null);
// spring.profiles.include + spring.profiles.active 配置的剖面
Set<Profile> activatedViaProperty = getProfilesActivatedViaProperty();
// 2. environment.getActiveProfiles() 過濾 activatedViaProperty 之後的剖面
// 目前看只有 SpringApplication 配置的 additionalProfiles
this.profiles.addAll(getOtherActiveProfiles(activatedViaProperty));
// 3. spring.profiles.include + spring.profiles.active
// addActiveProfiles 方法只能調用一次,前提是 activatedViaProperty 不為空
addActiveProfiles(activatedViaProperty);
// 4. spring.profiles.default
if (this.profiles.size() == 1) {
for (String defaultProfileName : this.environment.getDefaultProfiles()) {
Profile defaultProfile = new Profile(defaultProfileName, true);
this.profiles.add(defaultProfile);
}
}
}
Spring Boot 配置文件 Profiles(application-dev.properties) 的解析順序如下:
- 首先解析 null,也就是 application.properties 或 application.yml 文件
- spring.profiles.include/active 屬性配置之外的剖面先解析,一般是 activatedViaProperty 或其它編程式配置的 Profiles
- spring.profiles.include 定義的剖面,第三和第四的順序在 getProfilesActivatedViaProperty 中定義
- spring.profiles.active 定義的剖面
- spring.profiles.default 如果沒有激活的剖面,默認 default,即沒有 2、3、4 項
註意:實際讀取配置文件的順序和解析的相反,下面會詳細說明。 為什麽要這麽設計???
還有一種情況是在配置文件 application.properties 中定義了 spring.profiles.include/active 屬性的情況。加載到對應的配置文件後需要判斷是否定義了以上兩個屬性,如果定義了,也需要加載譔剖面對應的配置文件。
private void load(PropertySourceLoader loader, String location, Profile profile,
DocumentFilter filter, DocumentConsumer consumer) {
List<Document> loaded = new ArrayList<>();
for (Document document : documents) {
if (filter.match(document)) {
// 1. spring.profiles.active,如果已經定義了該方法就不會再執行了
addActiveProfiles(document.getActiveProfiles());
// 2. spring.profiles.include
addIncludedProfiles(document.getIncludeProfiles());
loaded.add(document);
}
}
}
// 毫無疑問,如果配置文件中定義了 spring.profiles.include 則需要先解析這些剖面,再解析其余的剖面
private void addIncludedProfiles(Set<Profile> includeProfiles) {
LinkedList<Profile> existingProfiles = new LinkedList<>(this.profiles);
this.profiles.clear();
// 1. 先解析配置文件中定義的 spring.profiles.include,當然如果已經解析了則需要移除
this.profiles.addAll(includeProfiles);
this.profiles.removeAll(this.processedProfiles);
// 2. 再解析剩余的剖面
this.profiles.addAll(existingProfiles);
}
3.3 配置文件解析
public void load() {
this.profiles = new LinkedList<>();
this.processedProfiles = new LinkedList<>();
this.activatedProfiles = false;
this.loaded = new LinkedHashMap<>();
// 1. this.profiles 定義了 profile 解析順序
initializeProfiles();
while (!this.profiles.isEmpty()) {
Profile profile = this.profiles.poll();
if (profile != null && !profile.isDefaultProfile()) {
addProfileToEnvironment(profile.getName());
}
// 2. 具體解析配置文件到 this.loaded 中
load(profile, this::getPositiveProfileFilter,
addToLoaded(MutablePropertySources::addLast, false));
this.processedProfiles.add(profile);
}
resetEnvironmentProfiles(this.processedProfiles);
// ??? 先忽略
load(null, this::getNegativeProfileFilter,
addToLoaded(MutablePropertySources::addFirst, true));
// 3. 加載配置文件到 environment 中,註意讀取配置文件的順序和解析的相反
addLoadedPropertySources();
}
- initializeProfiles 加載所有的剖面,解析時會按上面提到的順序進行解析
- load 具體解析配置文件到 this.loaded 中
- addLoadedPropertySources 加載配置文件到 environment 中,註意讀取配置文件的順序和解析的相反
每天用心記錄一點點。內容也許不重要,但習慣很重要!
Spring Boot 啟動(二) Environment 加載