基本介紹
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,并沒有啥不一樣,可能作者是留作以後擴充)
類的繼承關系圖譜:
涉及到的類
- 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源碼,分享學習過程,希望對各位有點微小的幫助。如有錯誤,請指正~)