1. 程式人生 > >SpringBoot原始碼解析之Config

SpringBoot原始碼解析之Config

SpringBoot配置檔案載入

1. ConfigFileApplicationListener

  1. 配置相關的屬性名基本都可以在這個類裡面找到. 例如 spring.profiles.active,application(配置檔案的名字;不過後綴名儲存在其他位置)等.
  2. 另外該類的註釋已經解釋得比較清楚了.
  3. 該類的載入位置是 spring-boot-1.5.7.RELEASE.jar下的META-INF/spring.factories中的 ApplicationListener, 而載入時機是構造SpringApplication例項時(參見本人部落格SpringBoot原始碼研究之Start
    中的第二節).

ConfigFileApplicationListener繼承鏈如下
ConfigFileApplicationListener繼承鏈

1.1 實現了SmartApplicationListener介面

// ------- SmartApplicationListener介面實現
@Override
public boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
	// 只支援`ApplicationEnvironmentPreparedEvent`和`ApplicationPreparedEvent`事件
	return ApplicationEnvironmentPreparedEvent.
class.isAssignableFrom(eventType) || ApplicationPreparedEvent.class.isAssignableFrom(eventType); } @Override public boolean supportsSourceType(Class<?> aClass) { // 支援所有的SourceType return true; } // -------- SmartApplicationListener介面的基介面ApplicationListener<ApplicationEvent>的實現 @Override
public void onApplicationEvent(ApplicationEvent event) { if (event instanceof ApplicationEnvironmentPreparedEvent) { onApplicationEnvironmentPreparedEvent( (ApplicationEnvironmentPreparedEvent) event); } if (event instanceof ApplicationPreparedEvent) { onApplicationPreparedEvent(event); } } // ApplicationEnvironmentPreparedEvent 型別事件 private void onApplicationEnvironmentPreparedEvent( ApplicationEnvironmentPreparedEvent event) { List<EnvironmentPostProcessor> postProcessors = loadPostProcessors(); // 將自身載入到集合中, 因為`ConfigFileApplicationListener`自身就實現了`EnvironmentPostProcessor`介面 postProcessors.add(this); AnnotationAwareOrderComparator.sort(postProcessors); for (EnvironmentPostProcessor postProcessor : postProcessors) { postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication()); } } List<EnvironmentPostProcessor> loadPostProcessors() { /* 從classpath下的每個jar中的"META-INF/spring.factories"中載入指定類, 例如這裡的條件下從spring-boot-1.5.7.RELEASE.jar中載入到的類如下: # Environment Post Processors org.springframework.boot.env.EnvironmentPostProcessor=\ org.springframework.boot.cloud.CloudFoundryVcapEnvironmentPostProcessor,\ org.springframework.boot.env.SpringApplicationJsonEnvironmentPostProcessor */ return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class, getClass().getClassLoader()); } // ApplicationPreparedEvent 型別事件 private void onApplicationPreparedEvent(ApplicationEvent event) { this.logger.replayTo(ConfigFileApplicationListener.class); addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext()); } /** * Add appropriate post-processors to post-configure the property-sources. * @param context the context to configure */ protected void addPostProcessors(ConfigurableApplicationContext context) { // 看PropertySourceOrderingPostProcessor名稱應該是對PropertySource進行排序 // 注意PropertySourceOrderingPostProcessor實現了Ordered介面, 而且優先順序是最高的. context.addBeanFactoryPostProcessor( new PropertySourceOrderingPostProcessor(context)); }

1.2 實現了EnvironmentPostProcessor介面

@Override
public void postProcessEnvironment(ConfigurableEnvironment environment,
		SpringApplication application) {
	// RandomValuePropertySource就是在這裡被加入到environment中的
	// 配置檔案中可以通過`${random.xx}`來獲取隨機值. 相關的類就是這個`RandomValuePropertySource`.
	// 細節可以看看 RandomValuePropertySource實現的 getProperty方法
	addPropertySources(environment, application.getResourceLoader());
	configureIgnoreBeanInfo(environment);
	bindToSpringApplication(environment, application);
}
// Add config file property sources to the specified environment.
protected void addPropertySources(ConfigurableEnvironment environment,
		ResourceLoader resourceLoader) {
	RandomValuePropertySource.addToEnvironment(environment);
	// 就是在這裡將配置檔案進行載入
	new Loader(environment, resourceLoader).load();
}

2. 時機

在繼續深入ConfigFileApplicationListener.Loader之前, 讓我們回頭看看Spring是如何執行到這裡的, 如下圖堆疊所示
堆疊資訊

private ConfigurableEnvironment prepareEnvironment(
		SpringApplicationRunListeners listeners,
		ApplicationArguments applicationArguments) {
	// 這裡的引數listeners的來源:
	// spring-boot-1.5.7.RELEASE.jar中的"META-INF/spring.factories"中的SpringApplicationRunListener實現.

	// Create and configure the environment
	// 這裡的getOrCreateEnvironment()方法會建立一個StandardServletEnvironment例項(web環境下,如何判斷請參考之前的文章).
	ConfigurableEnvironment environment = getOrCreateEnvironment();
	// 根據使用者傳入的引數對environment進行配置.
	configureEnvironment(environment, applicationArguments.getSourceArgs());
	// 觸發ApplicationEnvironmentPreparedEvent事件. 這也和上面的邏輯吻合上了.
	listeners.environmentPrepared(environment);
	if (!this.webEnvironment) {
	// 優秀的原始碼讀到一定的量之後, 應該就會有這樣一個感受: 
	// 它們很少將功能集中在一起, 而是進行拆分為一個個小的功能點;
	// 這也是新手在閱讀原始碼時會碰到比較大的問題之一, 跟著跳來跳去就暈了.
	// 這裡也是一樣, 當發現當前不是web環境時, 就委託給一個專門的類去處理轉換當前的environment. 這裡使用的類名和方法名已經很清楚地表明瞭所做的事情.
		environment = new EnvironmentConverter(getClassLoader())
				.convertToStandardEnvironmentIfNecessary(environment);
	}
	return environment;
}

3. ConfigFileApplicationListener.Loader

作為ConfigFileApplicationListener類的內部類, Loader負責對配置檔案進行載入.

public void load() {
	/* 這裡的PropertySourcesLoader會從`spring-boot-1.5.7.RELEASE.jar`下的`META-INF/spring.factories`中載入
	
# PropertySource Loaders 屬性檔案載入
org.springframework.boot.env.PropertySourceLoader=\
org.springframework.boot.env.PropertiesPropertySourceLoader,\
org.springframework.boot.env.YamlPropertySourceLoader	
	*/
	this.propertiesLoader = new PropertySourcesLoader();
	this.activatedProfiles = false;
	this.profiles = Collections.asLifoQueue(new LinkedList<Profile>());
	this.processedProfiles = new LinkedList<Profile>();

	// Pre-existing active profiles set via Environment.setActiveProfiles()
	// are additional profiles and config files are allowed to add more if
	// they want to, so don't call addActiveProfiles() here.
	Set<Profile> initialActiveProfiles = initializeActiveProfiles();
	this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));
	if (this.profiles.isEmpty()) {
		for (String defaultProfileName : this.environment.getDefaultProfiles()) {
			Profile defaultProfile = new Profile(defaultProfileName, true);
			if (!this.profiles.contains(defaultProfile)) {
				this.profiles.add(defaultProfile);
			}
		}
	}

	// The default profile for these purposes is represented as null. We add it
	// last so that it is first out of the queue (active profiles will then
	// override any settings in the defaults when the list is reversed later).
	this.profiles.add(null);

	while (!this.profiles.isEmpty()) {
		Profile profile = this.profiles.poll();
		for (String location : getSearchLocations()) {
			if (!location.endsWith("/")) {
				// location is a filename already, so don't search for more
				// filenames
				load(location, null, profile);
			}
			else {
				for (String name : getSearchNames()) {
					load(location, name, profile);
				}
			}
		}
		this.processedProfiles.add(profile);
	}
	
	// 將解析過後的資源資訊放置進Enviroment中propertySources屬性集合中
	addConfigurationProperties(this.propertiesLoader.getPropertySources());
}

5. 亮點

  1. ConfigFileApplicationListener類同時實現了EnvironmentPostProcessor介面和SmartApplicationListener介面,而在監聽回撥中又呼叫所有EnvironmentPostProcessor介面實現類, 這樣就將配置檔案的解析工作從主流程中剝離出來了,但其實又是集中在一個類裡面的。
  2. ConfigFileApplicationListener類中的內部類Loader類專職了配置檔案的解析和載入工作,這樣ConfigFileApplicationListener類的職責就更加單一了。

6. 總結

  1. ConfigFileApplicationListener類的例項通過spring-boot-1.5.7.RELEASE.jar下的META-INF/spring.factories配置檔案載入進容器。
  2. ConfigFileApplicationListener類通過監聽ApplicationEnvironmentPreparedEvent事件來進行自定義配置檔案的讀取。

7. Links

相關推薦

SpringBoot原始碼解析Config

SpringBoot配置檔案載入 1. ConfigFileApplicationListener類 配置相關的屬性名基本都可以在這個類裡面找到. 例如 spring.profiles.active,application(配置檔案的名字;不過後綴名儲存在

SpringBoot原始碼解析AutoConfiguration

自動配置絕對算得上是Spring Boot的最大亮點,完美的展示了CoC約定優於配置; 所以對其如何實現的探究可以為我們平時的工作和實踐中帶來一些參考和靈感,並且在實際使用SpringBoot減少所謂靈異事件的產生。 1. 概述 SpringBoot的Aut

springboot原始碼解析SpringApplication初始化、啟動

開發十年,就只剩下這套架構體系了! >>>   

springboot原始碼解析-管中窺豹系列總體結構(一)

# 一、簡介 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列專案型別(二)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列Runner(三)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列Initializer(四)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列排序(五)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列aware(六)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列web伺服器(七)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列BeanDefinition(八)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee

springboot原始碼解析-管中窺豹系列自動裝配(九)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列EnableXXX(十)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列BeanFactoryPostProcessor(十一)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列BeanDefine如何載入(十三)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

springboot原始碼解析-管中窺豹系列bean如何生成?(十四)

# 一、前言 - Springboot原始碼解析是一件大工程,逐行逐句的去研究程式碼,會很枯燥,也不容易堅持下去。 - 我們不追求大而全,而是試著每次去研究一個小知識點,最終聚沙成塔,這就是我們的springboot原始碼管中窺豹系列。 ![ 簡介 ](https://zhangbin1989.gitee.

Android框架原始碼解析(四)Picasso

這次要分析的原始碼是 Picasso 2.5.2 ,四年前的版本,用eclipse寫的,但不影響這次我們對其原始碼的分析 地址:https://github.com/square/picasso/tree/picasso-parent-2.5.2 Picasso的簡單使用

Android框架原始碼解析(三)ButterKnife

注:所有分析基於butterknife:8.4.0 原始碼目錄:https://github.com/JakeWharton/butterknife 其中最主要的3個模組是: Butterknife註解處理器https://github.com/JakeWharton/

Android框架原始碼解析(二)OKhttp

原始碼在:https://github.com/square/okhttp 包實在是太多了,OKhttp核心在這塊https://github.com/square/okhttp/tree/master/okhttp 直接匯入Android Studio中即可。 基本使用:

Android框架原始碼解析(一)Volley

前幾天面試CVTE,HR面掛了。讓內部一個學長幫我查看了一下面試官評價,發現二面面試官的評價如下: 廣度OK,但缺乏深究能力,深度與實踐不足 原始碼:只能說流程,細節程式碼不清楚,retrofit和volley都是。 感覺自己一方面:自己面試技巧有待提高吧(框