[享學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實現,只是功能上偏弱(比如不能控制順序、中間插入等等)

