SpringBoot擴(kuò)展外部化配置的原理解析
Environment實(shí)現(xiàn)原理
在基于SpringBoot開發(fā)的應(yīng)用中,我們常常會(huì)在application.properties
、application-xxx.properties
、application.yml
、application-xxx.yml
等配置文件中設(shè)置一些屬性值,然后通過@Value
、@ConfigurationProperties
等注解獲取,或者采用編碼的方式通過Environment
獲取。
# application.properties my.config.appId=demo
@RestController public class WebController { @Value("${my.config.appId}") private String appId; @Autowired private Environment env; @Autowired private ConfigurableEnvironment environment; @GetMapping("/appInfo") public String appInfo() { System.out.println(environment.getProperty("my.config.appId")); System.out.println(env.getProperty("my.config.appId")); System.out.println(appId); System.out.println(env == environment); //true return appId; } }
實(shí)際上env
和environment
是同一個(gè)對(duì)象,在Spring中ConfigurableEnvironment
是Environment
的子類,具體實(shí)現(xiàn)類全部是通過implements
ConfigurableEnvironment
接口來實(shí)現(xiàn),所以所有可以拿到Environment
接口地方都可以強(qiáng)制轉(zhuǎn)換為ConfigurableEnvironment
。
ConfigurableEnvironment
繼承Environment
,Environment
繼承PropertyResolver
,主要提供了對(duì)屬性獲取方法,AbstractEnvironment
做為抽象類實(shí)現(xiàn)了ConfigurableEnvironment
接口方法,其內(nèi)部是通過org.springframework.core.env.MutablePropertySources
來保存不同類型的屬性資源。而MutablePropertySources
內(nèi)部實(shí)際上就是List<PropertySource<?>>集合。
public interface ConfigurableEnvironment extends Environment, ConfigurablePropertyResolver { void setActiveProfiles(String... profiles); void addActiveProfile(String profile); void setDefaultProfiles(String... profiles); //MutablePropertySources 內(nèi)部實(shí)際上就是**List<PropertySource<?>>集合 MutablePropertySources getPropertySources(); Map<String, Object> getSystemProperties(); Map<String, Object> getSystemEnvironment(); void merge(ConfigurableEnvironment parent); }
PropertySource
是什么呢?
其實(shí)就是一個(gè)key-value集合,key就是一個(gè)配置項(xiàng),value就是配置的值。
例如: 通過System.getProperties()
得到的系統(tǒng)屬性就是一種類型的PropertySource
,通過application.yml
配置的屬性是另一種屬性資源。當(dāng)調(diào)用env.getProperty()
獲取屬性值時(shí),會(huì)遍歷PropertySource集合,只要有一個(gè)PropertySource
中有對(duì)應(yīng)屬性值則不再繼續(xù)遍歷查找,所以在集合中越靠前的屬性優(yōu)先級(jí)越高。
獲取某個(gè)配置項(xiàng)值的訪問方式,源碼如下:
org.springframework.core.env.PropertySourcesPropertyResolver#getProperty(java.lang.String, java.lang.Class<T>, boolean)
protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) { if (this.propertySources != null) { 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); } logKeyFound(key, propertySource, value); return convertValueIfNecessary(value, targetValueType); } } } if (logger.isTraceEnabled()) { logger.trace("Could not find key '" + key + "' in any property source"); } return null; }
如何擴(kuò)展自己的外部化配置?
實(shí)際上我們可以利用SpringBoot中的擴(kuò)展點(diǎn),拿到ConfigurableEnvironment
對(duì)象來獲取到MutablePropertySources
,添加自己的PropertySource
就行,例如可以訪問一個(gè)http接口,獲取外部化配置。
擴(kuò)展接口及優(yōu)先級(jí)如下
梯形縮進(jìn)表示內(nèi)部調(diào)用了下面的接口實(shí)現(xiàn)
1.org.springframework.boot.SpringApplicationRunListener#environmentPrepared(ConfigurableBootstrapContext, ConfigurableEnvironment)
1.ApplicationListener< org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent>
EnvironmentPostProcessorApplicationListener
1. org.springframework.boot.env.EnvironmentPostProcessor
1.org.springframework.boot.context.config.ConfigDataLoader
1.org.springframework.boot.env.PropertySourceLoader
1.org.springframework.context.ApplicationContextInitializer#initialize
1.org.springframework.boot.SpringApplicationRunListener#contextPrepared
4.org.springframework.boot.context.event.ApplicationPreparedEvent
5.org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor#postProcessBeanDefinitionRegistry
org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory
但是在4.BeanDefinitionRegistryPostProcessor
和5.BeanFactoryPostProcessor
中擴(kuò)展時(shí)機(jī)比較晚,這個(gè)時(shí)候已經(jīng)執(zhí)行完包掃描,如果在這個(gè)時(shí)機(jī)添加自己的外部化配置,對(duì)于注解@ConditionalOnProperty
可能大部分不會(huì)生效。
Apollo配置中心客戶端和SpringBoot的整合實(shí)現(xiàn)
Apollo配置中心客戶端是如何與SpringBoot整合的?
開源的Apollo配置中心默認(rèn)啟動(dòng)就是通過BeanFactoryPostProcessor
來擴(kuò)展apollo上的配置到Spring的Environment
中,
@EnableApolloConfig
注解向Spring中導(dǎo)入了bean com.ctrip.framework.apollo.spring.config.PropertySourcesProcessor
,PropertySourcesProcessor
同時(shí)實(shí)現(xiàn)了org.springframework.core.PriorityOrdered
并設(shè)置了最高的執(zhí)行優(yōu)先級(jí)Ordered.HIGHEST_PRECEDENCE
,但是由于包掃描已經(jīng)在PropertySourcesProcessor
之前執(zhí)行完成,所以即使設(shè)置了最高優(yōu)先級(jí),同樣無法解決在Spring執(zhí)行包掃描階段訪問不到apllo上的配置問題。
因此在SpringBoot項(xiàng)目中,apollo提供了另一種啟動(dòng)方式,使用配置項(xiàng)apollo.bootstrap.enabled = true
來解決,實(shí)現(xiàn)類為com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer
,其主要是通過實(shí)現(xiàn)第2個(gè)擴(kuò)展接口org.springframework.context.ApplicationContextInitializer
來提前將apollo的PropertySource
添加到Spring的Environment
中。
這樣我們就可以通過Environment
來獲取到apollo中的配置項(xiàng)值。而@ConditionalOnProperty
則是從Environment
獲取屬性值來判斷的條件是否成立,因此使用該接口擴(kuò)展Environment,@ConditionalOnProperty
注解則可以在啟動(dòng)階段正常訪問到apollo中的配置項(xiàng)。
到此這篇關(guān)于SpringBoot擴(kuò)展外部化配置的原理解析的文章就介紹到這了,更多相關(guān)SpringBoot擴(kuò)展外部化配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
在Spring Boot中使用Spring-data-jpa實(shí)現(xiàn)分頁查詢
如何使用jpa進(jìn)行多條件查詢以及查詢列表分頁呢?下面我將介紹兩種多條件查詢方式。具體實(shí)例代碼大家參考下本文吧2017-07-07java 操作gis geometry類型數(shù)據(jù)方式
這篇文章主要介紹了java 操作gis geometry類型數(shù)據(jù)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03java設(shè)置session過期時(shí)間的實(shí)現(xiàn)方法
這篇文章主要介紹了java設(shè)置session過期時(shí)間的實(shí)現(xiàn)方法,以實(shí)例形式詳細(xì)講述了具體實(shí)現(xiàn)過程,非常具有參考借鑒價(jià)值,需要的朋友可以參考下2014-10-10Java基礎(chǔ)之Unsafe內(nèi)存操作不安全類詳解
Java是面向?qū)ο笳Z言,在使用Java編程時(shí),大多數(shù)情況下都不會(huì)直接操作內(nèi)存,而且Java也不提倡直接操作內(nèi)存,但是Java中到底有沒有可以直接操作內(nèi)存的工具類呢?有!Java中提供Unsafe類可以用來來直接操作內(nèi)存,文中詳細(xì)介紹了Unsafe內(nèi)存操作不安全類,需要的朋友可以參考下2021-06-06Java編譯錯(cuò)誤信息提示java.lang.ExceptionInInitializer解決
這篇文章主要介紹了Java編譯錯(cuò)誤信息提示java.lang.ExceptionInInitializer的分析講解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07springboot -sse -flux 服務(wù)器推送消息的方法
這篇文章主要介紹了springboot -sse -flux 服務(wù)器推送消息的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-11-11java基礎(chǔ)之字符串編碼知識(shí)點(diǎn)總結(jié)
這篇文章主要介紹了java基礎(chǔ)之字符串編碼總結(jié),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有很好的幫助,要的朋友可以參考下2021-04-04