前言 本文針對版本2.2.0.RELEASE來分析SpringBoot的配置處理源碼,通過查看SpringBoot的源碼來弄清楚一些常見的問題比如: SpringBoot從哪里開始加載配置文件? SpringBoot從哪些地方加載配置文件? SpringBoot是如何支持yaml和properties類型的配置文件? 如果要支持json配置應該如何做? SpringBoot的配置優先級是怎么樣的? placeholder是如何被解析的? 帶著我們的問題一起去看一下SpringBoot配置相關的源代碼,找出問題的答案。 SpringBoot從哪里開始加載配置文件? SpringBoot加載配置文件的入口是由ApplicationEnvironmentPreparedEvent事件進入的,SpringBoot會在SpringApplication的構造函數中通過spring.factories文件獲取ApplicationListener的實例類: public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) { ... setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class)); ... } spring.factories中有一個ConfigFileApplicationListener類,它會監聽ApplicationEnvironmentPreparedEvent然后再加載配置文件 : # Application Listeners org.springframework.context.ApplicationListener= org.springframework.boot.context.config.ConfigFileApplicationListener ... 有了事件和事件處理的類后,再找出發送事件的地方,就可以搞清楚SpringBoot是怎么加載配置文件的了,SpringBoot在啟動之前先初始化好SpringApplicationRunListeners這個類,它會實現SpringApplicationRunListener接口然后對事件進行轉發: class SpringApplicationRunListeners { private final Log log; private final List<SpringApplicationRunListener> listeners; SpringApplicationRunListeners(Log log, Collection<? extends SpringApplicationRunListener> listeners) { this.log = log; this.listeners = new ArrayList<>(listeners); } void environmentPrepared(ConfigurableEnvironment environment) { for (SpringApplicationRunListener listener : this.listeners) { listener.environmentPrepared(environment); } } ... } 獲取SpringApplicationRunListeners的代碼如下: private SpringApplicationRunListeners getRunListeners(String[] args) { Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class }; return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args)); } 同樣也會去加載spring.factories文件,該文件有一個EventPublishingRunListener類,該類的作用就是SpringBoot的事件轉換成ApplicationEvent發送出去。 # Run Listeners org.springframework.boot.SpringApplicationRunListener=\ org.springframework.boot.context.event.EventPublishingRunListener 小結 SpringBoot會將事件轉換成ApplicationEvent再分發 SpringBoot是通過監聽ApplicationEnvironmentPreparedEvent事件來加載配置文件的 ConfigFileApplicationListener是處理配置文件的主要類 SpringBoot從哪些地方加載配置文件? 上面已經分析到ConfigFileApplicationListener是處理配置文件的主要類,然后進一步的查看SpringBoot是從哪些地址加載配置文件,進入ConfigFileApplicationListener類后會有兩個默認的常量: private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/"; private static final String DEFAULT_NAMES = "application"; 首先在沒有任何配置的情況下,會從DEFAULT_SEARCH_LOCATIONS常量列出來的位置中加載文件名為DEFAULT_NAMES(.properties或yml)的文件,默認位置包括: classpath根目錄(classpath:/) classpath里面的config文件目錄(classpath:/config/) 程序運行目錄(file:./) 程序運行目錄下的config目錄(file:./config/) 上面說的是沒有額外配置的情況,SpringBoot足夠靈活可以指定配置文件搜索路徑、配置文件名,在ConfigFileApplicationListener類中有個getSearchLocations方法,它主要負責獲取配置搜索目錄: private Set<String> getSearchLocations() { if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) { return getSearchLocations(CONFIG_LOCATION_PROPERTY); } Set<String> locations = getSearchLocations(CONFIG_ADDITIONAL_LOCATION_PROPERTY); locations.addAll( asResolvedSet(ConfigFileApplicationListener.this.searchLocations, DEFAULT_SEARCH_LOCATIONS)); return locations; } 它的操作步驟大致如下: 檢查是否有spring.config.location屬性,如果存在則直接使用它的值 從spring.config.additional-location屬性中獲取搜索路徑 將默認搜索路徑添加到搜索集合 這里就可以確定SpringBoot配置的搜索路徑有兩種情況:如果配置了spring.config.location則直接使用,否則使用spring.config.additional-location的屬性值 + 默認搜索路徑。 SpringBoot是如何支持yaml和properties類型的配置文件? SpringBoot的配置支持properties和yaml文件,SpringBoot是如何解析這兩種文件的呢,繼續分析ConfigFileApplicationListener這個類,里面有個子類叫Loader加載配置文件主要的工作就是由這貨負責,但是直接讀取properties和yaml并轉換成PropertySource還是由里面的PropertySourceLoader負責: Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { ... this.propertySourceLoaders = SpringFactoriesLoader.loadFactories(PropertySourceLoader.class, getClass().getClassLoader()); } 構造Loader對象的時候就會先加載PropertySourceLoader,加載方式還是從spring.factories中讀?。?# PropertySource Loaders org.springframework.boot.env.PropertySourceLoader=\ org.springframework.boot.env.PropertiesPropertySourceLoader,\ org.springframework.boot.env.YamlPropertySourceLoader 其中配置了兩個PropertySourceLoader的實現類: PropertiesPropertySourceLoader YamlPropertySourceLoader 看名字就知道是分別負責properties和yaml的啦。 如果要支持json配置應該如何做? 如果不喜歡properties和yaml這兩種格式,想要定義json做為配置文字格式可以直接定義json類型的PropertySourceLoader: public class JSONPropertySourceLoader implements PropertySourceLoader { @Override public String[] getFileExtensions() { return new String[] {"json"}; } @Override public List<PropertySource<?>> load(String name, Resource resource) throws IOException { if(resource == null || !resource.exists()){ return Collections.emptyList(); } Map<String, Object> configs = JSON.parseObject(resource.getInputStream(), Map.class); return Collections.singletonList( new MapPropertySource(name, configs) ); } } 然后在resources目錄里面建立個META-INF,再添加個spring.factories里面的內容如下: org.springframework.boot.env.PropertySourceLoader=\ com.csbaic.arch.spring.env.loader.JSONPropertySourceLoader 最后在resources目錄里面建個application.json的配置文件 : { "spring.application.name": "JSONConfig" } 正常啟動SpringBoot獲取spring.applicaiton.name的配置的值就是JSONConfig: 2019-11-02 14:50:17.730 INFO 55275 --- [ main] c.c.a.spring.env.SpringEnvApplication : JSONConfig SpringBoot的配置優先級是怎么樣的? SpringBoot中有個PropertySource接口,專門用來保存屬性常見的實現類有: CommandLinePropertySource MapPropertySource SystemEnvironmentPropertySource .... 另外為了集中管理PropertySource還抽象出一個PropertySources接口,PropertySources就一個實現類叫:MutablePropertySources,它將所有的PropertySource都放置在一個名叫propertySourceList集合中,同時提供一些修改操作方法: public void addFirst(PropertySource<?> propertySource) {} public void addLast(PropertySource<?> propertySource) {} public void addBefore(String relativePropertySourceName, PropertySource<?> propertySource) {} public void addAfter(String relativePropertySourceName, PropertySource<?> propertySource) {} public int precedenceOf(PropertySource<?> propertySource) { } public PropertySource<?> remove(String name) {} public void replace(String name, PropertySource<?> propertySource) {} 所有的PropertySource都保存在propertySourceList中,越小的索引優先級越高,所以如果想要覆蓋屬性只要保證優化級夠高就行。 placeholder是如何被解析的? 繼續分析ConfigFileApplicationListener的Loader子類,在構造時還會創建一個PropertySourcesPlaceholdersResolver,placeholder的解析都由它來完成: Loader(ConfigurableEnvironment environment, ResourceLoader resourceLoader) { this.placeholdersResolver = new PropertySourcesPlaceholdersResolver(this.environment); } 分析PropertySourcesPlaceholdersResolver發現,真正完成解析是由PropertyPlaceholderHelper完成,PropertySourcesPlaceholdersResolver 在構造的時候就會創建一個PropertyPlaceholderHelper public PropertySourcesPlaceholdersResolver(Iterable<PropertySource<?>> sources, PropertyPlaceholderHelper helper) { this.sources = sources; this.helper = (helper != null) ? helper : new PropertyPlaceholderHelper(SystemPropertyUtils.PLACEHOLDER_PREFIX, SystemPropertyUtils.PLACEHOLDER_SUFFIX, SystemPropertyUtils.VALUE_SEPARATOR, true); } PropertySourcesPlaceholdersResolver 在創建 PropertyPlaceholderHelper 的時候會傳遞三個參數:前綴、后綴、默認值分割符,分別由以下三個常量表示: public static final String PLACEHOLDER_PREFIX = "${"; public static final String PLACEHOLDER_SUFFIX = "}"; public static final String VALUE_SEPARATOR = ":"; 這樣 PropertyPlaceholderHelper 在解析placeholder時就能知道以什么格式來解析比如:${spring.application.name}這個placeholder就會被解析成屬性值。 總結 SpringBoot的配置非常靈活配置可以來自文件、環境變量、JVM系統屬性、配置中心等等,SpringBoot通過 PropertySource和PropertySources實現屬性優先級、CRUD的統一管理,為開發者提供統一的配置抽象。 《架構文摘》每天一篇架構領域重磅好文,涉及一線互聯網公司應用架構(高可用、高性 能、高穩定)、大數據、機器學習等各個熱門領域。
轉載自://www.cnblogs.com/xwgblog/p/11794207.html
欧美黄色网