<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring Cloud 配置知多少?

          共 8123字,需瀏覽 17分鐘

           ·

          2021-05-10 15:53

          點擊關注公眾號,Java干貨及時送達

          來源:https://zhenbianshu.github.io

          需求

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

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

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

          本文基于 Spring 5.0.5、Spring Boot 2.0.1 和 Spring Cloud 2.0.2。

          背景和問題

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

          Spring Boot 基礎教程就不介紹了,不會的看這里的示例教程和源碼:https://github.com/javastacks/spring-boot-best-practice

          properties 文件配置像:

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

          在使用時:

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

          這樣,Spring 會對 Test 類自動注入,將數組 [value1,value2,value3] 注入到 elements 屬性內。Spring Boot 學習筆記分享給你!

          而我們使用 Spring Cloud 自動加載配置的姿勢是這樣:

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

          使用 @RefreshScope 注解的類,在環(huán)境變量有變動后會自動重新加載,將最新的屬性注入到類屬性內,但它卻不支持數組的自動注入。

          而我的目標是能找到一種方式,使其即支持注入數組類型的屬性,又能使用 Spring Cloud 的自動刷新配置的特性。

          環(huán)境和屬性

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

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

          但 Spring 管理配置的最小單位并不是屬性,而是 PropertySource (屬性源),我們可以理解 PropertySource 是一個文件,或是某張配置數據表,Spring 在 Environment 內維護一個 PropertySourceList,當我們獲取配置時,Spring 從這些 PropertySource 內查找到對應的值,并使用 ConversionService 將值轉換為對應的類型返回。

          Spring Cloud 配置刷新機制

          分布式配置

          最新 Spring 系列教程,都在這了!

          Spring Cloud 內提供了 PropertySourceLocator 接口來對接 Spring 的 PropertySource 體系,通過 PropertySourceLocator,我們就拿到一個”自定義”的 PropertySource,Spring Cloud 里還有一個實現 ConfigServicePropertySourceLocator,通過它,我們可以定義一個遠程的 ConfigService,通過公用這個 ConfigService 來實現分布式的配置服務。

          ConfigClientProperties 這個配置類我們可以看得出來,它也為遠程配置預設了用戶名密碼等安全控制選項,還有 label 用來區(qū)分服務池等配置。

          scope 配置刷新

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

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

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

          public void refreshAll() {
           super.destroy();
           this.context.publishEvent(new RefreshScopeRefreshedEvent());
          }

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

          ConfigurationProperties 配置刷新

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

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

          它讀取了環(huán)境內所有 PropertySource 內的配置后,重新創(chuàng)建了一個 SpringApplication 以刷新配置,再次讀取所有配置項并得到與前面保存的配置項的對比,最后將前后配置差發(fā)布了一個 EnvironmentChangeEvent 事件。而 EnvironmentChangeEvent 的監(jiān)聽器是由 ConfigurationPropertiesRebinder 實現的,其主要邏輯在 rebind 方法。

          Object bean = this.applicationContext.getBean(name);
          if (AopUtils.isAopProxy(bean)) {
           bean = ProxyUtils.getTargetObject(bean);
          }
          if (bean != null) {
           this.applicationContext.getAutowireCapableBeanFactory().destroyBean(bean);
           this.applicationContext.getAutowireCapableBeanFactory().initializeBean(bean, name);
           return true;

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

          這里可以知道,Spring Cloud 在進行配置刷新時是考慮過 ConfigurationProperties 的,經過測試,在 ContextRefresher 刷新上下文后,ConfigurationProperties 注解類的屬性是會進行動態(tài)刷新的。

          測試一次就解決的事情,感覺有些白忙活了。。

          不過既然查到這里了,就再往下深入一些。

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

          接著我們再來看一下,環(huán)境里的屬性都是怎么在 Bean 創(chuàng)建時被使用的。

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

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

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

          private ConfigurationProperty findProperty(ConfigurationPropertyName name,
            Context context) {
           if (name.isEmpty()) {
            return null;
           }
           return context.streamSources()
             .map((source) -> source.getConfigurationProperty(name))
             .filter(Objects::nonNull).findFirst().orElse(null);
          }

          找到對應的屬性后,再使用 converter 將屬性轉換為對應的類型注入到 Bean 骨。

          private <T> Object bindProperty(Bindable<T> target, Context context,
            ConfigurationProperty property) {
           context.setConfigurationProperty(property);
           Object result = property.getValue();
           result = this.placeholdersResolver.resolvePlaceholders(result);
           result = context.getConverter().convert(result, target);
           return result;
          }

          一種 trick 方式

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

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

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

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

          @Component
          @RefreshScope  // 借用 Spring Cloud 實現此 Bean 的刷新
          public class ListSupportPropertyResolver {
              @Autowired
              ConfigurableEnvironment env; // 將環(huán)境注入到 Bean 內是修改環(huán)境的重要前提

              @PostConstruct
              public void init() {
                  // 將屬性鍵值對從環(huán)境內取出
                  Map<String, Object> properties = extract(env.getPropertySources());

                  // 解析環(huán)境里的數組,抽取出其中的數組配置
                  Map<String, List<String>> listProperties = collectListProperties(properties)
                  Map<String, Object> propertiesMap = new HashMap<>(listProperties);

                  MutablePropertySources propertySources = env.getPropertySources();
                  // 把數組配置生成一個 PropertySource 并放到環(huán)境的 PropertySourceList 內
                  propertySources.addFirst(new MapPropertySource("modifiedProperties", propertiesMap));
              }
          }

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

          當然了,有了比較”正規(guī)”的方式后,我們不必要對 PropertySource 進行修改,畢竟全局修改等于未知風險或埋坑。

          小結

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

          另外,關注公眾號Java技術棧,在后臺回復:面試,可以獲取我整理的 Java、Spring Cloud 系列面試題和答案,非常齊全。






          關注Java技術棧看更多干貨



          獲取 Spring Boot 實戰(zhàn)筆記!
          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  亚洲精品UV视频 | 亚洲婷婷激情一区 | 四虎在线视频 | a线视频免费观看:中文字幕 | 蜜芽欧美成人 |