[享學Netflix] 十、Archaius對Commons Configuration核心API Configuration的擴展實現

  • 2020 年 3 月 18 日
  • 筆記

要相信:也有很多人比你更勤奮 程式碼下載地址:https://github.com/f641385712/netflix-learning

目錄

前言

上篇文章體驗了一把Netflix Archaius的使用,感受到了它對配置管理上的便捷性。或許有小夥伴會說,配置管理上它和Apache Commons Configuration功能上有點重疊,其實不然。 他倆的關係不是功能重疊,而是Netflix Archaius是對Apache Commons Configuration的一種延伸,並且前者依賴於後者的實現。

因此本文將探討下Netflix Archaius它對Apache Commons configuration的擴展,在API層面做了哪些自定義以及使用上的增強。


正文

org.apache.commons.configuration.Configuration是Apache Commons configuration的核心介面,Netflix Archaius就是依賴了它來實現內部的配置源。

而我們知道Netflix Archaius它是一個高效的,執行緒安全的配置管理庫,因此它必然在Configuration的基礎上做了擴展和增強。


對Configuration的擴展

在API層面,它最重要的便是在org.apache.commons.configuration.Configuration上提供了擴展。主要分為兩個體系:ConcurrentMapConfiguration和AggregatedConfiguration。


ConcurrentMapConfiguration

該類使用ConcurrentHashMap讀取/寫入屬性以獲得高吞吐量和執行緒安全。其它實現均基於它。

public class ConcurrentMapConfiguration extends AbstractConfiguration {    	// Map用來存儲屬性值,是個ConcurrentHashMap  	// 其它集合之類的均是執行緒安全的~      protected Map<String,Object> map;      private Collection<ConfigurationListener> listeners = new CopyOnWriteArrayList<ConfigurationListener>();      private Collection<ConfigurationErrorListener> errorListeners = new CopyOnWriteArrayList<ConfigurationErrorListener>();      ...    	// 系統屬性。是否禁用分隔符去解析配置屬性  	// 比如若配置為不禁用,那麼1,2,3會被解析為List  	public static final String DISABLE_DELIMITER_PARSING = "archaius.configuration.disableDelimiterParsing";  	...    	@Override      public Object getProperty(String key) {          return map.get(key);      }      ...      @Override      public void setProperty(String key, Object value) throws ValidationException {            fireEvent(EVENT_SET_PROPERTY, key, value, true);          // 看看value是否允許解析,等等          // 如果允許解析,那就作為作為List<Object>放進去嘍          setPropertyImpl(key, value);          fireEvent(EVENT_SET_PROPERTY, key, value, false);      }  	... // 省略註冊事件、發送事件等其它方法的實現  }

如果熟悉Apache Commons Configuration的使用和源碼,此段程式碼沒啥特別的。若你還不熟悉,建議你從此處:[享學Netflix] 一、Apache Commons Configuration:你身邊的配置管理專家 開始了解。

它的繼承圖如下:


DynamicConfiguration

動態配置,它會依賴PolledConfigurationSource/AbstractPollingScheduler等來實現配置的動態化

public class DynamicConfiguration extends ConcurrentMapConfiguration {    	// 輪詢Scheduler      private AbstractPollingScheduler scheduler;      // 配置源      private PolledConfigurationSource source;    	// 說明:Commons Configuration的事件類型是通過int值區分的,此處定義一個100  	// 表示RELOAD重新載入事件  	public static final int EVENT_RELOAD = 100;      	// 注意這兩個構造器的區別  	// 無參構造:需要自己手動調用startPolling()才能啟動輪詢  	// 有參數構造:自動啟動輪詢      public DynamicConfiguration() {          super();      }      public DynamicConfiguration(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {          this();          startPolling(source, scheduler);      }    	// 啟動輪詢  	public synchronized void startPolling(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {          this.scheduler = scheduler;          this.source = source;    		// 空方法,鉤子方法,交給子類去實現  		init(source, scheduler);    		// 給Scheduler註冊監聽器:把事件類型轉換為對應的Commons Configuration事件發出去          scheduler.addPollListener(new PollListener() {              @Override              public void handleEvent(EventType eventType, PollResult lastResult, Throwable exception) {                  switch (eventType) {                      case POLL_SUCCESS:                          fireEvent(EVENT_RELOAD, null, null, false);                          break;                      case POLL_FAILURE:                          fireError(EVENT_RELOAD, null, null, exception);                          break;                      case POLL_BEGIN:                          fireEvent(EVENT_RELOAD, null, null, true);                          break;                  }              }          });    		// 啟動輪詢          scheduler.startPolling(source, this);  	}        public synchronized void stopLoading() {          if (scheduler != null) {              scheduler.stop();          }      }  }

這是一個自動動態性的org.apache.commons.configuration.Configuration。


DynamicURLConfiguration

它繼承自DynamicConfiguration,從名稱成也能知道,它使用的配置源是URLConfigurationSource。

public class DynamicURLConfiguration extends DynamicConfiguration {    	// URLConfigurationSource再熟悉不過了:默認會載入config.properties等等哦  	// 空構造就啟動了輪詢策略:因為已經制定了使用FixedDelay來輪詢(事件參數均為默認)      public DynamicURLConfiguration() {          URLConfigurationSource source = new URLConfigurationSource();          if (source.getConfigUrls() != null && source.getConfigUrls().size() > 0) {              startPolling(source, new FixedDelayPollingScheduler());          }      }    	// 當然嘍,還有個帶參構造  	public DynamicURLConfiguration(int initialDelayMillis, int delayMillis, boolean ignoreDeletesFromSource,  String... urls) { ... }    }

這個子類確定以及肯定了使用URLConfigurationSource和FixedDelayPollingScheduler來完成動態屬性輪詢,因此使用子類的case比使用父類多。


使用示例

使用DynamicURLConfiguration可以非常方便的讓屬性動態化:

@Test  public void fun4() throws InterruptedException {      DynamicURLConfiguration config = new DynamicURLConfiguration();        while (true) {          ConfigurationUtils.dump(config, System.out);          System.out.println();          TimeUnit.SECONDS.sleep(10);      }  }

控制台列印:

name=YourBatman  name=YourBatman  name=YourBatman  name=YourBatman-changed  ...

程式碼比上篇文章的實現優雅太多了有沒有。

說明:默認的輪詢時間延遲30秒執行(所以你看到先輸出了3個原始值),然後60秒輪詢一次


ClasspathPropertiesConfiguration

標準的模組化配置方法。

假設您的應用程式使用了許多模組**(.jar文件)和需求屬性支援,這個類提供了一個基於約定的方法**:從特定類路徑的每個jar中掃描和載入屬性位置,這個位置是"META-INF/conf/config.properties"。

public class ClasspathPropertiesConfiguration extends ConcurrentMapConfiguration {    	// 默認位置  此值可通過下面set方法修改,但不建議改  	static String propertiesResourceRelativePath = "META-INF/conf/config.properties";  	public static void setPropertiesResourceRelativePath() { ... }      	// 它通過下面的init方法初始化  	static ClasspathPropertiesConfiguration instance = null;      public static void initialize() {          try {              instance = new ClasspathPropertiesConfiguration();              // 載入此路徑下的config.properties唄(包括jar內的哦)              loadResources(propertiesResourceRelativePath);          } catch (Exception e) {              throw new RuntimeException("failed to read configuration properties from classpath", e);          }      }    	// 單例設計      private ClasspathPropertiesConfiguration() {}    	...  }

該實現是個單例設計,使用它僅需執行一次init操作即可。但是,但是,但是你會發現:它的Instance實例是不能包外訪問的,所以我們其實並不能直接使用它。

而它唯一使用地是WebApplicationProperties#init方法,由此可見一般用於Web環境。


DynamicWatchedConfiguration

一個特殊的配置,它的特點是:更改底層數據源,然後就可以手動去同步器屬性(也就是說可以換底層數據源…)。

// WatchedUpdateListener:根據WatchedUpdateResult對Config進行更新  public class DynamicWatchedConfiguration extends ConcurrentMapConfiguration implements WatchedUpdateListener {    	// 下面這幾個屬性都是前面講過的熟悉屬性  	// 該介面沒有任何實現,該介面提供data,並且可對data進行update/delete  	private final WatchedConfigurationSource source;  	private final boolean ignoreDeletesFromSource;  	private final DynamicPropertyUpdater updater;  	...  }
使用示例
private static class MyWatchedConfigurationSource implements WatchedConfigurationSource {        private final Map<String, Object> data;      private final List<WatchedUpdateListener> listeners;        public MyWatchedConfigurationSource(Map<String, Object> data) {          this.data = data;          listeners = new CopyOnWriteArrayList<>();      }        @Override      public void addUpdateListener(WatchedUpdateListener listener) {          listeners.add(listener);      }        @Override      public void removeUpdateListener(WatchedUpdateListener listener) {          listeners.remove(listener);      }        @Override      public Map<String, Object> getCurrentData() throws Exception {          return data;      }  }      @Test  public void fun5() {      Map<String, Object> data = new HashMap<>();      data.put("name", "YourBatman");        DynamicWatchedConfiguration config = new DynamicWatchedConfiguration(new MyWatchedConfigurationSource(data));      ConfigurationUtils.dump(config, System.out);      System.out.println("n");        // 改變屬性,會發現底層的Map也會改      data.put("age", 18);      // 注意:這一步是手動的。。。。      config.updateConfiguration(WatchedUpdateResult.createFull(data));      ConfigurationUtils.dump(config, System.out);  }

控制台輸出:

name=YourBatman    name=YourBatman  age=18

AggregatedConfiguration

Aggregated:聚合的,合計的。

public interface AggregatedConfiguration extends Configuration {      public void addConfiguration(AbstractConfiguration config);      public void addConfiguration(AbstractConfiguration config, String name);      public Set<String> getConfigurationNames();      public List<String> getConfigurationNameList();      public Configuration getConfiguration(String name);      public int getNumberOfConfigurations();      public Configuration getConfiguration(int index);      public List<AbstractConfiguration> getConfigurations();      public Configuration removeConfiguration(String name);      public boolean removeConfiguration(Configuration config);      public Configuration removeConfigurationAt(int index);  }

它有唯一實現類:ConcurrentCompositeConfiguration,同時也是ConcurrentMapConfiguration的子類。這個組合模式很重要,它是Netflix Archaius實現組合模式的核心要義。


ConcurrentCompositeConfiguration

此類在列表結構中維護配置的層次結構,要確定屬性值時,列表的順序代表配置的降序優先順序。這個規則和Spring的屬性源是一樣的,所以容易理解~

// 實現ConfigurationListener介面,是可以監聽ConfigurationEvent事件  // 注意:這個介面是Apache Commons Configuration的介面  public class ConcurrentCompositeConfiguration extends ConcurrentMapConfiguration  implements AggregatedConfiguration, ConfigurationListener, Cloneable {    	// 每個配置都給一個name名稱,不能重複(特別像Spring有木有)  	// 這裡Map裝的是有名詞的,key是所有的具名的  	// configList存儲的是所有的,不管有名字與否。所以List內容一般多餘Map  	private Map<String, AbstractConfiguration> namedConfigurations = new ConcurrentHashMap<>();  	private List<AbstractConfiguration> configList = new CopyOnWriteArrayList<>();  	...  	// 事件類型:  	public static final int EVENT_CONFIGURATION_SOURCE_CHANGED = 10001;  	...    	// clear() 清空configList、namedConfigurations  	// 然後放置一個默認的容器所屬的containerConfiguration      public ConcurrentCompositeConfiguration() {          clear();      }      // 指定容器所屬的containerConfiguration      public ConcurrentCompositeConfiguration(AbstractConfiguration containerConfiguration) { ... }      public ConcurrentCompositeConfiguration(AbstractConfiguration containerConfiguration, Collection<? extends AbstractConfiguration> configurations) { ... }      ...        public List<AbstractConfiguration> getConfigurations() {          return Collections.unmodifiableList(configList);      }      // 只返回有名字的哪些配置們 的名字們  	public List<String> getConfigurationNameList() { ... }  	// 在list里的位置,木有就是-1嘍      public int getIndexOfConfiguration(AbstractConfiguration config) {          return configList.indexOf(config);      }      public int getNumberOfConfigurations() {          return configList.size();      }      public int getIndexOfContainerConfiguration() {          return configList.indexOf(containerConfiguration);      }    	... // 下面是操作屬性的介面方法實現      @Override      public void setProperty(String key, Object value) {          containerConfiguration.setProperty(key, value);      }      @Override      public void addProperty(String key, Object value) {          containerConfiguration.addProperty(key, value);      }      ...      // 先從overrideProperties里找      // 再從configList里按照順序匹配,最先匹配上的為準      public Object getProperty(String key) {          if (overrideProperties.containsKey(key)) {              return overrideProperties.getProperty(key);          }          for (Configuration config : configList) { ... }          ...          return firstMatchingConfiguration.getProperty(key);  		return null; // 沒找到就返回null      }      // 邏輯類似      @Override      public boolean containsKey(String key) { ... }      ...  }

它就是一個組合模式,只不過擴展了些更多功能。如果了解Spring的PropertySource屬性源的小夥伴,閱讀這塊應該不太費力。


使用示例

略。


總結

關於Netflix Archaius對Commons Configuration核心API Configuration的擴展實現就介紹到這。

Netflix Archaius是基於Commons Configuration構建的,並且在其基礎上進行擴展,提供了執行緒安全、性能更高的Configuration供以使用,其中ConcurrentCompositeConfiguration更是它組合配置實現原理的核心要義。

說明:Commons Configuration也內置有CompositeConfiguration實現,只是功能上偏弱(比如不能控制順序、中間插入等等)

Tags: