apollo-怎麼把自己的配置放到 Spring 環境中

怎麼把自己的配置放到 Spring 環境中

Apollo 5 — 教你怎麼把自己的配置放到 Spring 環境中
參考URL: https://www.jianshu.com/p/cd6824f0672d

有的時候,你可能需要在 Spring 環境中放入一些配置,但這些配置無法寫死在配置文件中,只能運行時放入。那麼,這個時候該怎麼辦呢?

Apollo 就是搞配置的,那麼自然會遇到這個問題,他是如何處理的呢?

首先要知道 Spring 環境中,一個配置的數據結構是什麼?

是抽象類 PropertySource, 內部是個 key value 結構。這個 T 可以是任意類型,取決於子類的設計。

子類可以通過重寫 getProperty 抽象方法獲取配置。

Spring 自身的 org.springframework.core.env.MapPropertySource 就重寫了這個方法。

public class MapPropertySource extends EnumerablePropertySource<Map<String, Object>> {

    public MapPropertySource(String name, Map<String, Object> source) {
        super(name, source);
    }


    @Override
    public Object getProperty(String name) {
        return this.source.get(name);
    }

    @Override
    public boolean containsProperty(String name) {
        return this.source.containsKey(name);
    }

    @Override
    public String[] getPropertyNames() {
        return StringUtils.toStringArray(this.source.keySet());
    }
}

Apollo 就直接利用了這個類。

在這裏插入圖片描述兩個不同的子類,不同的刷新邏輯。我們暫時不關心他們的不同。

這兩個類都會被 RefreshableConfig 組合,添加到 Spring 的環境中。

import org.springframework.core.env.ConfigurableEnvironment;

public abstract class RefreshableConfig {

  @Autowired
  private ConfigurableEnvironment environment; // Spring 環境

  @PostConstruct
  public void setup() {
  // 省略代碼
    for (RefreshablePropertySource propertySource : propertySources) {
      propertySource.refresh();
      // 注意:成功刷新後,放到 Spring 的環境中
      environment.getPropertySources().addLast(propertySource);
    }
  // 省略代碼

當從 Spring 的環境中獲取配置的時候,具體代碼是下面這樣的

protected <T> T getProperty(String key, Class<T> targetValueType, boolean resolveNestedPlaceholders) {
        for (PropertySource<?> propertySource : this.propertySources) {
            // 注意:這裏調用的就是 propertySource.getProperty 方法,子類剛剛重寫的方法
            Object value = propertySource.getProperty(key);
             // 省略無關代碼........
            return convertValueIfNecessary(value, targetValueType);
        }
        return null;
}

Spring 維護了一個 PropertySource 的集合,這個結合是有順序的,也就是說,排在最前面的優先級最高(遍歷從下標 0 開始)

而用戶可以在 PropertySource 裏,維護一個配置字典(Map),這樣,就類似 2 維數組的這樣一個數據結構。

所以,配置是可以重名的,重名時,以最前面的 PropertySource 中的配置爲準。所以,Spring 留給了幾個 API:

addFirst(PropertySource<?> propertySource)
addLast(PropertySource<?> propertySource)
addBefore(String relativePropertySourceName, PropertySource<?> propertySource)
addAfter(String relativePropertySourceName, PropertySource<?> propertySource)

從名字可以看出,通過這些 API,我們可以將 propertySource 插入到我們指定的地方。從而可以手動控制配置的優先級。

Spring 中有個現成的 CompositePropertySource 類,內部聚合了一個 PropertySource Set 集合,當 getProperty(String name) 的時候,就會遍歷這個集合,然後調用這個 propertySource 的 getProperty(name) 方法。相當於 3 維數組。

在這裏插入圖片描述一個環境中,有多個 PS(PropertySource 簡稱),每個 PS 可以直接包含配置,也可以再包裝一層 PS。

簡單例子
我們這裏有個簡單的例子,需求:
程序裏有個配置,但不能寫死在配置文件中,只能在程序啓動過程中進行配置,然後注入到 Spring 環境中,讓 Spring 在之後的 IOC 中,可以正常的使用這些配置。

@SpringBootApplication
public class DemoApplication {

  @Value("${timeout:1}")
  String timeout;


  public static void main(String[] args) throws InterruptedException {
    ApplicationContext c = SpringApplication.run(DemoApplication.class, args);
    for (; ; ) {
      Thread.sleep(1000);
      System.out.println(c.getBean(DemoApplication.class).timeout);
    }
  }
}

application.properties 配置文件
timeout=100

上面的代碼中,我們在 bean 中定義了一個屬性 timeout, 並在本地配置文件中寫入了一個 100 的值,也在表達式中給了一個默認值 1。

那麼現在打印出來的就是配置文件中的值:100.

但是,這不是我們想要的結果,所以需要修改代碼。

我們加入一個類:

@Component
class Test implements EnvironmentAware, BeanFactoryPostProcessor {

  @Override
  public void setEnvironment(Environment environment) {
    ((ConfigurableEnvironment) environment).getPropertySources()
        // 這裏是 addFirst,優先級高於 application.properties 配置
        .addFirst(new PropertySource<String>("timeoutConfig", "12345") {
          // 重點
          @Override
          public Object getProperty(String s) {
            if (s.equals("timeout")) {//
              return source;// 返回構造方法中的 source :12345
            }
            return null;
          }
        });
  }

  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)
      throws BeansException {
    // NOP
  }
}

運行之後,結果:12345

爲什麼加入了這個類,就能夠代替配置文件中的屬性呢?解釋一下這個類的作用。

我們要做的事情就是在 Spring 的環境中,插入自定義的 PS 對象,以便容器獲取的時候,能夠通過 getProperty 方法獲取對應的配置。

所以,我們要拿到 Spring 環境對象,還需要創建一個 PS 對象,並重寫 getProperty 方法,同時,注意:自己的 PS 配置優先級需要高於容器配置文件的優先級,保險起見,放在第一位。

PS 構造方法的第一個參數沒什麼用,就是一個標識符,第二個參數就是 source,可以定義爲任何類型,String,Map,都可以,我們這裏簡單期間,就是一個 String,直接返回這個值,如果是 Map,就調用 Map 的 get 方法。

爲什麼要實現 BeanFactoryPostProcessor 接口呢? 實現 BeanFactoryPostProcessor 接口的目的是讓該 Bean 的加載時機提前,高於目標 Bean 的初始化。否則,目標 Bean 中的 timeout 屬性都注入結束了,後面的操作就沒有意義了。

總結:需要拿到 Spirng 的環境對象,並向環境中添加自定義的 PS 對象,重寫 PS 的 getProperty 方法,即可獲取配置(注意優先級)。還需要注意加載這個配置的 bean 的優先級也要很高,通常實BeanFactoryPostProcessor 接口就足夠了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章