天天看點

spring5/springboot2源碼學習 -- Environment及PropertySource相關

基本介紹

Environment是spring中一個非常核心的接口,是spring屬性加載的基礎,代表目前應用運作的環境。

可以分為兩個方面:

  • profiles:設定profiles
  • properties:查找屬性配置

接口定義

public interface Environment extends PropertyResolver {
   
   String[] getActiveProfiles();

   String[] getDefaultProfiles();

   boolean acceptsProfiles(String... profiles);

}
           

可以發現Environment接口中隻有profiles相關的方法,因為其查找屬性的功能由其父接口PropertyResolver提供:

public interface PropertyResolver {

        boolean containsProperty(String key);
        String getProperty(String key);
        String getProperty(String key, String defaultValue);
        <T> T getProperty(String key, Class<T> targetType);
        <T> T getProperty(String key, Class<T> targetType, T defaultValue);
        String getRequiredProperty(String key) throws IllegalStateException;
        <T> T getRequiredProperty(String key, Class<T> targetType) throws IllegalStateException;
        String resolvePlaceholders(String text);
        String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;

    }
           

常用實作類

StandardEnvironment:對應非servlet應用(新的webflux子產品也是web,其environment的實作也是這個)

StandardServletEnvironment:對應servlet應用(spring5之前就是web應用)

StandardReactiveWebEnvironment:當使用web-reactive子產品時,用的是這個實作(其實這個就是單純繼承了StandardEnvironment,并沒有啥不一樣,可能作者是留作以後擴充)

類的繼承關系圖譜:

spring5/springboot2源碼學習 -- Environment及PropertySource相關

涉及到的類

  • PropertySource:用于儲存各個來源的配置項(總共有20個類)
  • PropertySources:組合多個PropertySource(總共有2個類)
  • PropertyResolver:具體執行屬性查找的類,内部主要也是通過PropertySources來實作功能(總共有4個類)
  • PropertyPlaceholderHelper:解析嵌套的配置屬性的工具類(總共有1個類)
  • Environment:(總共有9個類)
  • ConversionService:如果找到的屬性類型與需要的類型不比對,就會用ConversionService來轉化為需要的類型
  • EnvironmentCapable:指明對象包含了一個Environment并暴露了擷取Environment的接口,是以的ApplicationContext都實作了這個接口(總共有1個類)
  • PropertySourceLoader:spring boot中用于加載屬性配置檔案(總共有3個類)
  • PropertiesLoaderUtils:加載xml屬性配置檔案的工具類(總共有1個類)
  • PropertySourceFactory:處理@PropertySource、@PropertySources注解(總共有4個類)

實作原理

關于profiles相關功能的實作其實很好了解,用一個Set儲存所有active的profile就行了。

關鍵在于getProperty()相關功能的實作。

在spring的Environment中,引入了一個PropertySources用于表示PropertySource的集合,而PropertySource又代表這些表示屬性的鍵值對的來源,比如:配置檔案(properties檔案)、map、ServletConfig等。

通常一個spring boot應用啟動後,其environment會有如下的幾個PropertySource:

name type 含義
commandLineArgs SimpleCommandLinePropertySource 指令行參數
servletConfigInitParams ServletContextPropertySource ServletConfig參數
servletContextInitParams ServletConfigPropertySource ServletContext啟動參數
spring.application.json SpringApplicationJsonEnvironmentPostProcessor.JsonPropertySource spring.application.json配置項
systemProperties MapPropertySource 來源于System.getProperties()方法
systemEnvironment SystemEnvironmentPropertySourceEPP.OriginAwareSystemEnvironmentPropertySource 來源于System.getEnv()方法
random RandomValuePropertySource 為random.int/long開頭的屬性賦一個随機值
applicationConfig OriginTrackedMapPropertySource 配置檔案對應的propertySource,根據activeProfiles的情況,可能有多個

PropertySource的來源

systemProperties或者systemEnvironment對應的PropertySource都很容易獲得,但是諸如application.properties配置檔案對應的PropertySource是如何從一個普通的配置檔案變為PropertySource的呢?

答案是通過PropertySourceLoader加載而來 or 通過@PropertySource注解

PropertySourceLoader

關于PropertySourceLoader:

public interface PropertySourceLoader {
	//檔案擴充名,比如properties,yaml
	String[] getFileExtensions();
	List<PropertySource<?>> load(String name, Resource resource) throws IOException;
}
           

這個接口有兩個子類:

  • PropertiesPropertySourceLoader:加載.properties檔案,以及.xml檔案
  • YamlPropertySourceLoader:加載.yaml檔案

@PropertySource

當spring在解析配置檔案時,如果發現了@PropertySource注解,則會通過PropertySourceFactory的預設實作類DefaultPropertySourceFactory去加載@PropertySource中指定的屬性檔案,将其解析成ResourcePropertySource

方法執行過程分析

//不論已那種getProperty的重載為入口,終究會走進這個方法
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
		if (this.propertySources != null) {
      //按照順序,周遊所有的PropertySource
			for (PropertySource<?> propertySource : this.propertySources) {
				if (logger.isTraceEnabled()) {
					logger.trace("Searching for key '" + key + "' in PropertySource '" +
							propertySource.getName() + "'");
				}
				Object value = propertySource.getProperty(key);
				if (value != null) {
          //如果找到了
					if (resolveNestedPlaceholders && value instanceof String) {
            //解析嵌套的配置項
						value = resolveNestedPlaceholders((String) value);
					}
          //打一個debug日志
					logKeyFound(key, propertySource, value);
          //轉換為需要的類型
					return convertValueIfNecessary(value, targetValueType);
				}
			}
		}
		if (logger.isTraceEnabled()) {
			logger.trace("Could not find key '" + key + "' in any property source");
		}
		return null;
	}
           

可以發現,其實,Environment的實作類,自己是沒有實作getProperty()方法的,查找屬性相關的方法都是由PropertySourcesPropertyResolver來具體實作的,其實作原理大緻上可以描述為:

  • 将PropertySource組裝成PropertySources對象,這個PropertySources接口其實就是一系列PropertySource的集合
  • 挨個找PropertySource中的配置項
  • 如果找到了,再看這個配置項的value裡面是不是有嵌套的配置項,比如你可能在配置檔案中配置了:a.b=aaa${server.port}bbb這種,這是需要去解析這些嵌套的配置項
  • 因為getProperty()有個重載的方法,需要傳回指定的類型,是以如果進過之前的步驟找到的配置項類型不滿足,會調用ConversionService來轉化為需要的類型

其他的關聯使用

EnvironmentPostProcessor

定義:

@FunctionalInterface
public interface EnvironmentPostProcessor {
  //當ApplicationEnvironmentPreparedEvent事件發生時,執行方法
	void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application);
}
           

EnvironmentPostProcessor是spring的一個擴充點,如果想在Environment建立好時,進行一些處理,可以通過自定義一個EnvironmentPostProcessor來實作。

做法:

  • 建立一個META-INF/spring.factories檔案
  • 新增一個配置項org.springframework.boot.env.EnvironmentPostProcessor=你的具體實作類的全路徑類名

spring boot中的已有實作:

  • CloudFoundryVcapEnvironmentPostProcessor
  • SystemEnvironmentPropertySourceEnvironmentPostProcessor:将propertySources中的systemEnvironment從SystemEnvironmentPropertySource替換為内部類OriginAwareSystemEnvironmentPropertySource
  • SpringApplicationJsonEnvironmentPostProcessor:解析spring.application.json/SPRING_APPLICATION_JSON,并将其作為一個map PropertySource添加到Environment中
  • ConfigFileApplicationListener
  • DebugAgentEnvironmentPostProcessor:reactor相關,沒看
  • SpringBootTestRandomPortEnvironmentPostProcessor:測試用

結語

Environment及PropertySource,總共涉及了52個類左右,四舍五入,又是千裡之行的10裡路了!

(水準有限,最近在看spring源碼,分享學習過程,希望對各位有點微小的幫助。如有錯誤,請指正~)

繼續閱讀