SpringBoot擴(kuò)展外部化配置的原理解析
Environment實(shí)現(xiàn)原理
在基于SpringBoot開(kāi)發(fā)的應(yīng)用中,我們常常會(huì)在application.properties、application-xxx.properties、application.yml、application-xxx.yml等配置文件中設(shè)置一些屬性值,然后通過(guò)@Value、@ConfigurationProperties等注解獲取,或者采用編碼的方式通過(guò)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的子類(lèi),具體實(shí)現(xiàn)類(lèi)全部是通過(guò)implements ConfigurableEnvironment接口來(lái)實(shí)現(xiàn),所以所有可以拿到Environment接口地方都可以強(qiáng)制轉(zhuǎn)換為ConfigurableEnvironment。
ConfigurableEnvironment繼承Environment,Environment繼承PropertyResolver,主要提供了對(duì)屬性獲取方法,AbstractEnvironment做為抽象類(lèi)實(shí)現(xiàn)了ConfigurableEnvironment接口方法,其內(nèi)部是通過(guò)org.springframework.core.env.MutablePropertySources來(lái)保存不同類(lèi)型的屬性資源。而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就是配置的值。
例如: 通過(guò)System.getProperties()得到的系統(tǒng)屬性就是一種類(lèi)型的PropertySource,通過(guò)application.yml配置的屬性是另一種屬性資源。當(dāng)調(diào)用env.getProperty()獲取屬性值時(shí),會(huì)遍歷PropertySource集合,只要有一個(gè)PropertySource中有對(duì)應(yīng)屬性值則不再繼續(xù)遍歷查找,所以在集合中越靠前的屬性?xún)?yōu)先級(jí)越高。
獲取某個(gè)配置項(xiàng)值的訪問(wèn)方式,源碼如下:
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ì)象來(lái)獲取到MutablePropertySources,添加自己的PropertySource就行,例如可以訪問(wèn)一個(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#postProcessBeanDefinitionRegistryorg.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配置中心客戶(hù)端和SpringBoot的整合實(shí)現(xiàn)
Apollo配置中心客戶(hù)端是如何與SpringBoot整合的?
開(kāi)源的Apollo配置中心默認(rèn)啟動(dòng)就是通過(guò)BeanFactoryPostProcessor來(lái)擴(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í),同樣無(wú)法解決在Spring執(zhí)行包掃描階段訪問(wèn)不到apllo上的配置問(wèn)題。
因此在SpringBoot項(xiàng)目中,apollo提供了另一種啟動(dòng)方式,使用配置項(xiàng)apollo.bootstrap.enabled = true來(lái)解決,實(shí)現(xiàn)類(lèi)為com.ctrip.framework.apollo.spring.boot.ApolloApplicationContextInitializer,其主要是通過(guò)實(shí)現(xiàn)第2個(gè)擴(kuò)展接口org.springframework.context.ApplicationContextInitializer來(lái)提前將apollo的PropertySource添加到Spring的Environment中。
這樣我們就可以通過(guò)Environment來(lái)獲取到apollo中的配置項(xiàng)值。而@ConditionalOnProperty則是從Environment獲取屬性值來(lái)判斷的條件是否成立,因此使用該接口擴(kuò)展Environment,@ConditionalOnProperty注解則可以在啟動(dòng)階段正常訪問(wèn)到apollo中的配置項(xiàng)。
到此這篇關(guān)于SpringBoot擴(kuò)展外部化配置的原理解析的文章就介紹到這了,更多相關(guān)SpringBoot擴(kuò)展外部化配置內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java8中NIO緩沖區(qū)(Buffer)的數(shù)據(jù)存儲(chǔ)詳解
在本篇文章中小編給大家分享了關(guān)于java8中NIO緩沖區(qū)(Buffer)的數(shù)據(jù)存儲(chǔ)的相關(guān)知識(shí)點(diǎn),需要的朋友們參考下。2019-04-04
基于visualvm監(jiān)控類(lèi)實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了基于visualvm監(jiān)控類(lèi)實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-09-09
解決RestTemplate第一次請(qǐng)求響應(yīng)速度較慢的問(wèn)題
這篇文章主要介紹了解決RestTemplate第一次請(qǐng)求響應(yīng)速度較慢的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10
Java的Spring框架中bean的繼承與內(nèi)部bean的注入
這篇文章主要介紹了Java的Spring框架中bean的繼承與內(nèi)部bean的注入,Spring框架是Java的SSH三大web開(kāi)發(fā)框架之一,需要的朋友可以參考下2015-12-12
java?安全?ysoserial?CommonsCollections6?分析
這篇文章主要介紹了java?安全?ysoserial?CommonsCollections6示例分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
Java通過(guò)調(diào)用C/C++實(shí)現(xiàn)的DLL動(dòng)態(tài)庫(kù)——JNI的方法
這篇文章主要介紹了Java通過(guò)調(diào)用C/C++實(shí)現(xiàn)的DLL動(dòng)態(tài)庫(kù)——JNI的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Java多線程教程之如何利用Future實(shí)現(xiàn)攜帶結(jié)果的任務(wù)
Callable與Future兩功能是Java?5版本中加入的,這篇文章主要給大家介紹了關(guān)于Java多線程教程之如何利用Future實(shí)現(xiàn)攜帶結(jié)果任務(wù)的相關(guān)資料,需要的朋友可以參考下2021-12-12
一文帶你搞懂Java中Synchronized和Lock的原理與使用
這篇文章主要為大家詳細(xì)介紹了Java中Synchronized和Lock的原理與使用,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Java有一定的幫助,需要的可以參考一下2023-04-04

