成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從 Spring 的環(huán)境到 Spring Cloud 的配置

開發(fā) 后端
雖然在解決需求問題時(shí)走了些彎路,但也借此機(jī)會(huì)了解了 Spring Cloud 的一部分,抽空總結(jié)一下問題和在查詢問題中了解到的知識(shí),分享出來讓再遇到此問題的同學(xué)少踩坑吧。

 [[435246]]

需求

不知不覺,web 開發(fā)已經(jīng)進(jìn)入 “微服務(wù)”、”分布式” 的時(shí)代,致力于提供通用 Java 開發(fā)解決方案的 Spring 自然不甘人后,提出了 Spring Cloud 來擴(kuò)大 Spring 在微服務(wù)方面的影響,也取得了市場(chǎng)的認(rèn)可,在我們的業(yè)務(wù)中也有應(yīng)用。

前些天,我在一個(gè)需求中也遇到了 spring cloud 的相關(guān)問題。我們?cè)谟玫氖?Spring Cloud 的 config 模塊,它是用來支持分布式配置的,原來單機(jī)配置在使用了 Spring Cloud 之后,可以支持第三方存儲(chǔ)配置和配置的動(dòng)態(tài)修改和重新加載,自己在業(yè)務(wù)代碼里實(shí)現(xiàn)配置的重新加載,Spring Cloud 將整個(gè)流程抽離為框架,并很好的融入到 Spring 原有的配置和 Bean 模塊內(nèi)。

雖然在解決需求問題時(shí)走了些彎路,但也借此機(jī)會(huì)了解了 Spring Cloud 的一部分,抽空總結(jié)一下問題和在查詢問題中了解到的知識(shí),分享出來讓再遇到此問題的同學(xué)少踩坑吧。

背景和問題

我們的服務(wù)原來有一批單機(jī)的配置,由于同一 key 的配置太長(zhǎng),于是將其配置為數(shù)組的形式,并使用 Spring Boot 的 @ConfigurationProperties 和 @Value 注解來解析為 Bean 屬性。

properties 文件配置像: 

  1. test.config.elements[0]=value1  
  2. test.config.elements[1]=value2  
  3. test.config.elements[2]=value3 

在使用時(shí): 

  1. @ConfigurationProperties(prefix="test.config" 
  2. Class Test{  
  3.     @Value("${#elements}")  
  4.     private String[] elements;  

這樣,Spring 會(huì)對(duì) Test 類自動(dòng)注入,將數(shù)組 [value1,value2,value3] 注入到 elements 屬性內(nèi)。

而我們使用 Spring Cloud 自動(dòng)加載配置的姿勢(shì)是這樣: 

  1. @RefreshScope  
  2. class Test{  
  3.     @Value("${test.config.elements}")  
  4.     private String[] elements;  

使用 @RefreshScope 注解的類,在環(huán)境變量有變動(dòng)后會(huì)自動(dòng)重新加載,將最新的屬性注入到類屬性內(nèi),但它卻不支持?jǐn)?shù)組的自動(dòng)注入。

而我的目標(biāo)是能找到一種方式,使其即支持注入數(shù)組類型的屬性,又能使用 Spring Cloud 的自動(dòng)刷新配置的特性。

環(huán)境和屬性

無論Spring Cloud 的特性如何優(yōu)秀,在 Spring 的地盤,還是要入鄉(xiāng)隨俗,和 Spring 的基礎(chǔ)組件打成一片。所以為了了解整個(gè)流程,我們就要先了解 Spring 的基礎(chǔ)。

Spring 是一個(gè)大容器,它不光存儲(chǔ) Bean 和其中的依賴,還存儲(chǔ)著整個(gè)應(yīng)用內(nèi)的配置,相對(duì)于 BeanFactory 存儲(chǔ)著各種 Bean,Spring 管理環(huán)境配置的容器就是 Environment,從 Environment 內(nèi),我們能根據(jù) key 獲取所有配置,還能根據(jù)不同的場(chǎng)景(Profile,如 dev,test,prod)來切換配置。

但 Spring 管理配置的最小單位并不是屬性,而是 PropertySource (屬性源),我們可以理解 PropertySource 是一個(gè)文件,或是某張配置數(shù)據(jù)表,Spring 在 Environment 內(nèi)維護(hù)一個(gè) PropertySourceList,當(dāng)我們獲取配置時(shí),Spring 從這些 PropertySource 內(nèi)查找到對(duì)應(yīng)的值,并使用 ConversionService 將值轉(zhuǎn)換為對(duì)應(yīng)的類型返回。

Spring Cloud 配置刷新機(jī)制

分布式配置

Spring Cloud 內(nèi)提供了 PropertySourceLocator 接口來對(duì)接 Spring 的 PropertySource 體系,通過 PropertySourceLocator,我們就拿到一個(gè)”自定義”的 PropertySource,Spring Cloud 里還有一個(gè)實(shí)現(xiàn) ConfigServicePropertySourceLocator,通過它,我們可以定義一個(gè)遠(yuǎn)程的 ConfigService,通過公用這個(gè) ConfigService 來實(shí)現(xiàn)分布式的配置服務(wù)。

從 ConfigClientProperties 這個(gè)配置類我們可以看得出來,它也為遠(yuǎn)程配置預(yù)設(shè)了用戶名密碼等安全控制選項(xiàng),還有 label 用來區(qū)分服務(wù)池等配置。

scope 配置刷新

遠(yuǎn)程配置有了,接下來就是對(duì)變化的監(jiān)測(cè)和基于配置變化的刷新。

Spring Cloud 提供了 ContextRefresher 來幫助我們實(shí)現(xiàn)環(huán)境的刷新,其主要邏輯在 refreshEnvironment 方法和 scope.refreshAll() 方法,我們分開來看。

我們先來看 spring cloud 支持的 scope.refreshAll 方法。 

  1. public void refreshAll() {  
  2.  super.destroy();  
  3.  this.context.publishEvent(new RefreshScopeRefreshedEvent());  

scope.refreshAll 則更”野蠻”一些,直接銷毀了 scope,并發(fā)布了一個(gè) RefreshScopeRefreshedEvent 事件,scope 的銷毀會(huì)導(dǎo)致 scope 內(nèi)(被 RefreshScope 注解)所有的 bean 都會(huì)被銷毀。而這些被強(qiáng)制設(shè)置為 lazyInit 的 bean 再次創(chuàng)建時(shí),也就完成了新配置的重新加載。

ConfigurationProperties 配置刷新

然后再回過頭來看 refreshEnvironment 方法。 

  1. Map<String, Object> before = extract(this.context.getEnvironment().getPropertySources());  
  2.   addConfigFilesToEnvironment();  
  3.   Set<String> keys = changes(before,extract(this.context.getEnvironment().getPropertySources())).keySet();  
  4.   this.context.publishEvent(new EnvironmentChangeEvent(context, keys));  
  5.   return keys; 

它讀取了環(huán)境內(nèi)所有 PropertySource 內(nèi)的配置后,重新創(chuàng)建了一個(gè) SpringApplication 以刷新配置,再次讀取所有配置項(xiàng)并得到與前面保存的配置項(xiàng)的對(duì)比,最后將前后配置差發(fā)布了一個(gè) EnvironmentChangeEvent 事件。

而 EnvironmentChangeEvent 的監(jiān)聽器是由 ConfigurationPropertiesRebinder 實(shí)現(xiàn)的,其主要邏輯在 rebind 方法。 

  1. Object bean = this.applicationContext.getBean(name);  
  2. if (AopUtils.isAopProxy(bean)) {  
  3.  bean = ProxyUtils.getTargetObject(bean);  
  4.  
  5. if (bean != null) { 
  6.  this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);  
  7.              this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);  
  8.  return true; 

可以看到它的處理邏輯,就是把其內(nèi)部存儲(chǔ)的 ConfigurationPropertiesBeans 依次執(zhí)行銷毀邏輯,再執(zhí)行初始化邏輯實(shí)現(xiàn)屬性的重新綁定。

這里可以知道,Spring Cloud 在進(jìn)行配置刷新時(shí)是考慮過 ConfigurationProperties 的,經(jīng)過測(cè)試,在 ContextRefresher 刷新上下文后,ConfigurationProperties 注解類的屬性是會(huì)進(jìn)行動(dòng)態(tài)刷新的。

測(cè)試一次就解決的事情,感覺有些白忙活了。。不過既然查到這里了,就再往下深入一些。

Bean 的創(chuàng)建與環(huán)境

接著我們?cè)賮砜匆幌拢h(huán)境里的屬性都是怎么在 Bean 創(chuàng)建時(shí)被使用的。

我們知道,Spring 的 Bean 都是在 BeanFactory 內(nèi)創(chuàng)建的,創(chuàng)建邏輯的入口在 AbstractBeanFactory.doGetBean(name, requiredType, args, false) 方法,而具體實(shí)現(xiàn)在 AbstractAutowireCapableBeanFactory.doCreateBean 方法內(nèi),在這個(gè)方法里,實(shí)現(xiàn)了 Bean 實(shí)例的創(chuàng)建、屬性填充、初始化方法調(diào)用等邏輯。

在這里,有一個(gè)非常復(fù)雜的步驟就是調(diào)用全局的 BeanPostProcessor,這個(gè)接口是 Spring 為 Bean 創(chuàng)建準(zhǔn)備的勾子接口,實(shí)現(xiàn)這個(gè)接口的類可以對(duì) Bean 創(chuàng)建時(shí)的操作進(jìn)行修改。它是一個(gè)非常重要的接口,是我們能干涉 Spring Bean 創(chuàng)建流程的重要入口。

我們要說的是它的一種具體實(shí)現(xiàn) ConfigurationPropertiesBindingPostProcessor,它通過調(diào)用鏈 ConfigurationPropertiesBinder.bind() --> Binder.bindObject() --> Binder.findProperty() 方法查找環(huán)境內(nèi)的屬性。 

  1. private ConfigurationProperty findProperty(ConfigurationPropertyName name,  
  2.    Context context) {  
  3.   if (name.isEmpty()) {  
  4.    return null;  
  5.   }  
  6.   return context.streamSources()  
  7.     .map((source) -> source.getConfigurationProperty(name))  
  8.     .filter(Objects::nonNull).findFirst().orElse(null);  
  9.  } 

找到對(duì)應(yīng)的屬性后,再使用 converter 將屬性轉(zhuǎn)換為對(duì)應(yīng)的類型注入到 Bean 骨。 

  1. private <T> Object bindProperty(Bindable<T> target, Context context,  
  2.   ConfigurationProperty property) {  
  3.  context.setConfigurationProperty(property);  
  4.  Object result = property.getValue();  
  5.  result = this.placeholdersResolver.resolvePlaceholders(result);  
  6.  result = context.getConverter().convert(result, target);  
  7.  return result;  

一種 trick 方式

由上面可以看到,Spring 是支持 @ConfigurationProperties 屬性的動(dòng)態(tài)修改的,但在查詢流程時(shí),我也找到了一種比較 trick 的方式。

我們先來整理動(dòng)態(tài)屬性注入的關(guān)鍵點(diǎn),再從這些關(guān)鍵點(diǎn)里找可修改點(diǎn)。

  •  PropertySourceLocator 將 PropertySource 從遠(yuǎn)程數(shù)據(jù)源引入,如果這時(shí)我們能修改數(shù)據(jù)源的結(jié)果就能達(dá)到目的,可是 Spring Cloud 的遠(yuǎn)程資源定位器 ConfigServicePropertySourceLocator 和 遠(yuǎn)程調(diào)用工具 RestTemplate 都是實(shí)現(xiàn)類,如果生硬地對(duì)其繼承并修改,代碼很不優(yōu)雅。
  •  Bean 創(chuàng)建時(shí)會(huì)依次使用 BeanPostProcessor 對(duì)上下文進(jìn)行操作。這時(shí)添加一個(gè) BeanPostProcessor,可以手動(dòng)實(shí)現(xiàn)對(duì) Bean 屬性的修改。但這種方式 實(shí)現(xiàn)起來很復(fù)雜,而且由于每一個(gè) BeanPostProcessor 在所有 Bean 創(chuàng)建時(shí)都會(huì)調(diào)用,可能會(huì)有安全問題。
  •  Spring 會(huì)在解決類屬性注入時(shí),使用 PropertyResolver 將配置項(xiàng)解析為類屬性指定的類型。這時(shí)候添加屬性解析器 PropertyResolver 或類型轉(zhuǎn)換器 ConversionService 可以插手屬性的操作。但它們都只負(fù)責(zé)處理一個(gè)屬性,由于我的目標(biāo)是”多個(gè)”屬性變成一個(gè)屬性,它們也無能為力。

我這里能想到的方式是借用 Spring 自動(dòng)注入的能力,把 Environment Bean 注入到某個(gè)類中,然后在類的初始化方法里對(duì) Environment 內(nèi)的 PropertySource 里進(jìn)行修改,也可以達(dá)成目的,這里貼一下偽代碼。 

  1. @Component  
  2. @RefreshScope  // 借用 Spring Cloud 實(shí)現(xiàn)此 Bean 的刷新  
  3. public class ListSupportPropertyResolver {  
  4.     @Autowired 
  5.     ConfigurableEnvironment env; // 將環(huán)境注入到 Bean 內(nèi)是修改環(huán)境的重要前提  
  6.     @PostConstruct  
  7.     public void init() {  
  8.         // 將屬性鍵值對(duì)從環(huán)境內(nèi)取出  
  9.         Map<String, Object> properties = extract(env.getPropertySources());  
  10.         // 解析環(huán)境里的數(shù)組,抽取出其中的數(shù)組配置  
  11.         Map<String, List<String>> listProperties = collectListProperties(properties)  
  12.         Map<String, Object> propertiesMap = new HashMap<>(listProperties);  
  13.         MutablePropertySources propertySources = env.getPropertySources();  
  14.         // 把數(shù)組配置生成一個(gè) PropertySource 并放到環(huán)境的 PropertySourceList 內(nèi)  
  15.         propertySources.addFirst(new MapPropertySource("modifiedProperties", propertiesMap));  
  16.     }  

這樣,在創(chuàng)建 Bean 時(shí),就能第一優(yōu)先級(jí)使用我們修改過的 PropertySource 了。

當(dāng)然了,有了比較”正規(guī)”的方式后,我們不必要對(duì) PropertySource 進(jìn)行修改,畢竟全局修改等于未知風(fēng)險(xiǎn)或埋坑。

小結(jié)

查找答案的過程中,我更深刻地理解到 Environment、BeanFactory 這些才是 Spring 的基石,框架提供的各種花式功能都是基于它們實(shí)現(xiàn)的,對(duì)這些知識(shí)的掌握,對(duì)于理解它表現(xiàn)出來的高級(jí)特性很有幫助,之后再查找框架問題也會(huì)更有方向。 

 

責(zé)任編輯:龐桂玉 來源: Java知音
相關(guān)推薦

2019-08-15 10:56:10

WebServletSpring mvc

2023-11-01 15:07:51

環(huán)境配置方式

2017-12-01 08:54:18

SpringCloudHystrix

2025-02-25 00:11:40

Servlet服務(wù)器Web

2017-11-14 09:03:36

Spring Clou架構(gòu)演進(jìn)

2017-11-13 15:48:36

架構(gòu)Spring Clou演進(jìn)

2017-10-31 14:58:11

Spring Clou配置信息問題

2018-06-01 23:08:01

Spring Clou微服務(wù)服務(wù)器

2017-09-20 09:46:38

Spring BootSpring Clou內(nèi)存

2018-07-27 15:43:24

Spring Clou管理架構(gòu)

2023-12-19 09:33:40

微服務(wù)監(jiān)控

2021-03-10 09:21:00

Spring開源框架Spring基礎(chǔ)知識(shí)

2025-06-09 01:01:00

2022-08-11 09:17:38

架構(gòu)開發(fā)

2020-07-03 08:00:11

Spring BootSpring Clou流程

2021-06-04 08:48:46

Spring ClouMaven Centr版本

2017-04-12 14:43:01

Spring ClouZuul過濾器

2022-05-24 14:07:53

OpenFeignSpring開源

2018-06-22 15:46:45

Spring Clou加密處理

2019-08-22 09:55:17

RedisAPI數(shù)據(jù)
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 日本视频在线播放 | 国产一区在线看 | 亚洲国产aⅴ成人精品无吗 亚洲精品久久久一区二区三区 | 日日操操| 国产精品欧美精品 | 日本午夜一区 | 国产一级黄色网 | 精品久久国产视频 | 四虎影视免费观看 | 国产91在线 | 亚洲 | 国产综合精品一区二区三区 | 91精品国产美女在线观看 | 天天干天天操 | 色婷婷综合在线观看 | 羞羞视频在线免费 | 成年视频在线观看福利资源 | 成人精品一区二区三区中文字幕 | 国产在线视频在线观看 | 国产综合视频 | 在线午夜 | 毛片视频网址 | 欧美午夜一区 | 亚洲精品永久免费 | 一区中文字幕 | 免费精品在线视频 | 亚洲久视频 | 中文字幕免费 | 欧美一级片 | 久久久久久免费精品一区二区三区 | 亚洲不卡在线视频 | 在线视频 亚洲 | 久产久精国产品 | 岛国毛片在线观看 | 中文亚洲字幕 | 欧美在线观看一区二区 | 人人看人人爽 | 欧美a级成人淫片免费看 | 伊人伊成久久人综合网站 | 在线日韩| 国产精品免费观看 | 日韩视频在线免费观看 |