Spring?Cloud?中自定義外部化擴(kuò)展機(jī)制原理及實(shí)戰(zhàn)記錄
Spring Cloud針對(duì)Environment的屬性源功能做了增強(qiáng),
在spring-cloud-contenxt這個(gè)包中,提供了PropertySourceLocator接口,用來(lái)實(shí)現(xiàn)屬性文件加載的擴(kuò)展。我們可以通過(guò)這個(gè)接口來(lái)擴(kuò)展自己的外部化配置加載。這個(gè)接口的定義如下
public interface PropertySourceLocator { /** * @param environment The current Environment. * @return A PropertySource, or null if there is none. * @throws IllegalStateException if there is a fail-fast condition. */ PropertySource<?> locate(Environment environment); }
locate這個(gè)抽象方法,需要返回一個(gè)PropertySource對(duì)象。
這個(gè)PropertySource就是Environment中存儲(chǔ)的屬性源。 也就是說(shuō),我們?nèi)绻獙?shí)現(xiàn)自定義外部化配置加載,只需要實(shí)現(xiàn)這個(gè)接口并返回PropertySource即可。
按照這個(gè)思路,我們按照下面幾個(gè)步驟來(lái)實(shí)現(xiàn)外部化配置的自定義加載。
自定義PropertySource
既然PropertySourceLocator需要返回一個(gè)PropertySource,那我們必須要定義一個(gè)自己的PropertySource,來(lái)從外部獲取配置來(lái)源。
GpDefineMapPropertySource 表示一個(gè)以Map結(jié)果作為屬性來(lái)源的類(lèi)。
public class GpDefineMapPropertySource extends MapPropertySource { /** * Create a new {@code MapPropertySource} with the given name and {@code Map}. * * @param name the associated name * @param source the Map source (without {@code null} values in order to get * consistent {@link #getProperty} and {@link #containsProperty} behavior) */ public GpDefineMapPropertySource(String name, Map<String, Object> source) { super(name, source); } @Override public Object getProperty(String name) { return super.getProperty(name); public String[] getPropertyNames() { return super.getPropertyNames(); }
擴(kuò)展PropertySourceLocator
擴(kuò)展PropertySourceLocator,重寫(xiě)locate提供屬性源。
而屬性源是從gupao.json文件加載保存到自定義屬性源GpDefineMapPropertySource中。
public class GpJsonPropertySourceLocator implements PropertySourceLocator { //json數(shù)據(jù)來(lái)源 private final static String DEFAULT_LOCATION="classpath:gupao.json"; //資源加載器 private final ResourceLoader resourceLoader=new DefaultResourceLoader(getClass().getClassLoader()); @Override public PropertySource<?> locate(Environment environment) { //設(shè)置屬性來(lái)源 GpDefineMapPropertySource jsonPropertySource=new GpDefineMapPropertySource ("gpJsonConfig",mapPropertySource()); return jsonPropertySource; } private Map<String,Object> mapPropertySource(){ Resource resource=this.resourceLoader.getResource(DEFAULT_LOCATION); if(resource==null){ return null; } Map<String,Object> result=new HashMap<>(); JsonParser parser= JsonParserFactory.getJsonParser(); Map<String,Object> fileMap=parser.parseMap(readFile(resource)); processNestMap("",result,fileMap); return result; //加載文件并解析 private String readFile(Resource resource){ FileInputStream fileInputStream=null; try { fileInputStream=new FileInputStream(resource.getFile()); byte[] readByte=new byte[(int)resource.getFile().length()]; fileInputStream.read(readByte); return new String(readByte,"UTF-8"); } catch (IOException e) { e.printStackTrace(); }finally { if(fileInputStream!=null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return null; //解析完整的url保存到result集合。 為了實(shí)現(xiàn)@Value注入 private void processNestMap(String prefix,Map<String,Object> result,Map<String,Object> fileMap){ if(prefix.length()>0){ prefix+="."; for (Map.Entry<String, Object> entrySet : fileMap.entrySet()) { if (entrySet.getValue() instanceof Map) { processNestMap(prefix + entrySet.getKey(), result, (Map<String, Object>) entrySet.getValue()); } else { result.put(prefix + entrySet.getKey(), entrySet.getValue()); }
Spring.factories
在/META-INF/spring.factories
文件中,添加下面的spi擴(kuò)展,讓Spring Cloud啟動(dòng)時(shí)掃描到這個(gè)擴(kuò)展從而實(shí)現(xiàn)GpJsonPropertySourceLocator的加載。
org.springframework.cloud.bootstrap.BootstrapConfiguration=\ com.gupaoedu.env.GpJsonPropertySourceLocator
編寫(xiě)controller測(cè)試
@RestController public class ConfigController { @Value("${custom.property.message}") private String name; @GetMapping("/") public String get(){ String msg=String.format("配置值:%s",name); return msg; } }
階段性總結(jié)
通過(guò)上述案例可以發(fā)現(xiàn),基于Spring Boot提供的PropertySourceLocator擴(kuò)展機(jī)制,可以輕松實(shí)現(xiàn)自定義配置源的擴(kuò)展。
于是,引出了兩個(gè)問(wèn)題。
- PropertySourceLocator是在哪個(gè)被觸發(fā)的?
- 既然能夠從
gupao.json
中加載數(shù)據(jù)源,是否能從遠(yuǎn)程服務(wù)器上加載呢?
PropertySourceLocator加載原理
先來(lái)探索第一個(gè)問(wèn)題,PropertySourceLocator的執(zhí)行流程。
SpringApplication.run
在spring boot項(xiàng)目啟動(dòng)時(shí),有一個(gè)prepareContext的方法,它會(huì)回調(diào)所有實(shí)現(xiàn)了ApplicationContextInitializer
的實(shí)例,來(lái)做一些初始化工作。
ApplicationContextInitializer是Spring框架原有的東西, 它的主要作用就是在,ConfigurableApplicationContext類(lèi)型(或者子類(lèi)型)的ApplicationContext做refresh之前,允許我們對(duì)ConfiurableApplicationContext的實(shí)例做進(jìn)一步的設(shè)置和處理。
它可以用在需要對(duì)應(yīng)用程序上下文進(jìn)行編程初始化的web應(yīng)用程序中,比如根據(jù)上下文環(huán)境來(lái)注冊(cè)propertySource,或者配置文件。而Config 的這個(gè)配置中心的需求恰好需要這樣一個(gè)機(jī)制來(lái)完成。
public ConfigurableApplicationContext run(String... args) { //省略代碼... prepareContext(context, environment, listeners, applicationArguments, printedBanner); //省略代碼 return context; }
PropertySourceBootstrapConfiguration.initialize
其中,PropertySourceBootstrapConfiguration就實(shí)現(xiàn)了ApplicationContextInitializer
,initialize
方法代碼如下。
@Override public void initialize(ConfigurableApplicationContext applicationContext) { List<PropertySource<?>> composite = new ArrayList<>(); //對(duì)propertySourceLocators數(shù)組進(jìn)行排序,根據(jù)默認(rèn)的AnnotationAwareOrderComparator AnnotationAwareOrderComparator.sort(this.propertySourceLocators); boolean empty = true; //獲取運(yùn)行的環(huán)境上下文 ConfigurableEnvironment environment = applicationContext.getEnvironment(); for (PropertySourceLocator locator : this.propertySourceLocators) { //回調(diào)所有實(shí)現(xiàn)PropertySourceLocator接口實(shí)例的locate方法,并收集到source這個(gè)集合中。 Collection<PropertySource<?>> source = locator.locateCollection(environment); if (source == null || source.size() == 0) { //如果source為空,直接進(jìn)入下一次循環(huán) continue; } //遍歷source,把PropertySource包裝成BootstrapPropertySource加入到sourceList中。 List<PropertySource<?>> sourceList = new ArrayList<>(); for (PropertySource<?> p : source) { sourceList.add(new BootstrapPropertySource<>(p)); } logger.info("Located property source: " + sourceList); composite.addAll(sourceList);//將source添加到數(shù)組 empty = false; //表示propertysource不為空 } //只有propertysource不為空的情況,才會(huì)設(shè)置到environment中 if (!empty) { //獲取當(dāng)前Environment中的所有PropertySources. MutablePropertySources propertySources = environment.getPropertySources(); String logConfig = environment.resolvePlaceholders("${logging.config:}"); LogFile logFile = LogFile.get(environment); // 遍歷移除bootstrapProperty的相關(guān)屬性 for (PropertySource<?> p : environment.getPropertySources()) { if (p.getName().startsWith(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { propertySources.remove(p.getName()); } } //把前面獲取到的PropertySource,插入到Environment中的PropertySources中。 insertPropertySources(propertySources, composite); reinitializeLoggingSystem(environment, logConfig, logFile); setLogLevels(applicationContext, environment); handleIncludedProfiles(environment); } }
上述代碼邏輯說(shuō)明如下。
1.首先this.propertySourceLocators
,表示所有實(shí)現(xiàn)了PropertySourceLocators
接口的實(shí)現(xiàn)類(lèi),其中就包括我們前面自定義的GpJsonPropertySourceLocator
。
2.根據(jù)默認(rèn)的 AnnotationAwareOrderComparator 排序規(guī)則對(duì)propertySourceLocators數(shù)組進(jìn)行排序。
3.獲取運(yùn)行的環(huán)境上下文ConfigurableEnvironment
4.遍歷propertySourceLocators時(shí)
- 調(diào)用 locate 方法,傳入獲取的上下文environment
- 將source添加到PropertySource的鏈表中
- 設(shè)置source是否為空的標(biāo)識(shí)標(biāo)量empty
5.source不為空的情況,才會(huì)設(shè)置到environment中返回Environment的可變形式,可進(jìn)行的操作如addFirst、addLast移除propertySources中的bootstrapProperties根據(jù)config server覆寫(xiě)的規(guī)則,設(shè)置propertySources處理多個(gè)active profiles的配置信息
- 返回Environment的可變形式,可進(jìn)行的操作如addFirst、addLast
- 移除propertySources中的bootstrapProperties
- 根據(jù)config server覆寫(xiě)的規(guī)則,設(shè)置propertySources
- 處理多個(gè)active profiles的配置信息
注意:this.propertySourceLocators這個(gè)集合中的PropertySourceLocator,是通過(guò)自動(dòng)裝配機(jī)制完成注入的,具體的實(shí)現(xiàn)在BootstrapImportSelector
這個(gè)類(lèi)中。
ApplicationContextInitializer的理解和使用
ApplicationContextInitializer是Spring框架原有的東西, 它的主要作用就是在,ConfigurableApplicationContext類(lèi)型(或者子類(lèi)型)的ApplicationContext做refresh之前,允許我們對(duì)ConfiurableApplicationContext的實(shí)例做進(jìn)一步的設(shè)置和處理。
它可以用在需要對(duì)應(yīng)用程序上下文進(jìn)行編程初始化的web應(yīng)用程序中,比如根據(jù)上下文環(huán)境來(lái)注冊(cè)propertySource,或者配置文件。而Config 的這個(gè)配置中心的需求恰好需要這樣一個(gè)機(jī)制來(lái)完成。
創(chuàng)建一個(gè)TestApplicationContextInitializer
public class TestApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext>{ @Override public void initialize(ConfigurableApplicationContext applicationContext) { ConfigurableEnvironment ce=applicationContext.getEnvironment(); for(PropertySource<?> propertySource:ce.getPropertySources()){ System.out.println(propertySource); } System.out.println("--------end"); } }
添加spi加載
創(chuàng)建一個(gè)文件/resources/META-INF/spring.factories。添加如下內(nèi)容
org.springframework.context.ApplicationContextInitializer= \ com.gupaoedu.example.springcloudconfigserver9091.TestApplicationContextInitializer
在控制臺(tái)就可以看到當(dāng)前的PropertySource的輸出結(jié)果。
ConfigurationPropertySourcesPropertySource {name='configurationProperties'} StubPropertySource {name='servletConfigInitParams'} StubPropertySource {name='servletContextInitParams'} PropertiesPropertySource {name='systemProperties'} OriginAwareSystemEnvironmentPropertySource {name='systemEnvironment'} RandomValuePropertySource {name='random'} MapPropertySource {name='configServerClient'} MapPropertySource {name='springCloudClientHostInfo'} OriginTrackedMapPropertySource {name='applicationConfig: [classpath:/application.yml]'} MapPropertySource {name='kafkaBinderDefaultProperties'} MapPropertySource {name='defaultProperties'} MapPropertySource {name='springCloudDefaultProperties'}
到此這篇關(guān)于Spring Cloud 中自定義外部化擴(kuò)展機(jī)制原理及實(shí)戰(zhàn)的文章就介紹到這了,更多相關(guān)Spring Cloud 擴(kuò)展機(jī)制原理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Spring?Boot2.6.0新特性之默認(rèn)禁止循環(huán)引用
Spring?Boot2.6.0為我們帶來(lái)很多好用的新特性/改進(jìn),這篇文章主要給大家介紹了關(guān)于Spring?Boot2.6.0新特性之默認(rèn)禁止循環(huán)引用的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-02-02Java實(shí)現(xiàn)的模糊匹配某文件夾下的文件并刪除功能示例
這篇文章主要介紹了Java實(shí)現(xiàn)的模糊匹配某文件夾下的文件并刪除功能,涉及java針對(duì)目錄與文件的遍歷、匹配、判斷、刪除等相關(guān)操作技巧,需要的朋友可以參考下2018-02-02java調(diào)用ffmpeg實(shí)現(xiàn)轉(zhuǎn)換視頻
這篇文章主要為大家詳細(xì)介紹了java調(diào)用ffmpeg實(shí)現(xiàn)轉(zhuǎn)換視頻功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-12-12解決logback-classic 使用testCompile的打包問(wèn)題
這篇文章主要介紹了解決logback-classic 使用testCompile的打包問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Java之對(duì)象銷(xiāo)毀和finalize方法的使用
這篇文章主要介紹了Java之對(duì)象銷(xiāo)毀和finalize方法的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07jpa多條件查詢重寫(xiě)Specification的toPredicate方法
這篇文章主要介紹了多條件查詢重寫(xiě)Specification的toPredicate方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-11-11Spring Boot假死診斷實(shí)戰(zhàn)記錄
這篇文章主要給大家介紹了關(guān)于Spring Boot假死診斷的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Spring中屬性注入的幾種方式以及復(fù)雜屬性的注入詳解
這篇文章主要介紹了Spring中屬性注入的幾種方式以及復(fù)雜屬性的注入詳解,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04