[享學Netflix] 十二、Archaius動態屬性DynamicProperty原理詳解(重要)
- 2020 年 3 月 18 日
- 筆記
要相信:使用這些類庫,你的程式碼將變得更好。 程式碼下載地址:https://github.com/f641385712/netflix-learning
目錄
前言
上篇文章了解到了Netflix Archaius
它提供的兩個支援類:配置管理器ConfigurationManager
和動態屬性支援DynamicPropertySupport
。特別是DynamicPropertySupport
它提供了對動態屬性的支援,原理便是通過PropertyListener
來完成。
本文將深入了解Netflix Archaius
動態屬性相關內容,它用DynamicProperty
表示一個動態k-v屬性值,再結合DynamicPropertySupport
提供的監聽支援,最終實現屬性動態化。
正文
DynamicProperty
:動態屬性,一個實例對象代表一個k-v,然後具有動態的能力 DynamicPropertyFactory
:用於構建一個動態屬性實例,屏蔽掉DynamicProperty
具體實現,對使用者友好。
DynamicProperty
一個帶有快取的動態配置屬性,它具有動態性:當屬性變更時會自動更新其快取。該對象是執行緒安全的,並且訪問速度非常快,高於System.getProperty()
。
說明:效率高於System得益於
ConcurrentHashMap
的效率高於Properties
(Hashtable)
使用場景:該類用於多次獲取屬性值,並且該值可能會動態更改的情況。如果屬性只讀一次,則應使用「普通」訪問方法。如果屬性值是固定的,請考慮將該值快取在變數中(用變數表示)。
public class DynamicProperty { private volatile static DynamicPropertySupport dynamicPropertySupportImpl; // 這就是它速度快的原因。注意是static的喲,需要放置記憶體泄漏 private static final ConcurrentHashMap<String, DynamicProperty> ALL_PROPS = new ConcurrentHashMap<>(); ... private String propName; // 屬性名 private String stringValue = null; // 屬性值 private long changedTime; private CopyOnWriteArraySet<Runnable> callbacks = new CopyOnWriteArraySet<>(); ... // 重要抽象:特定類型的快取Value // 它的特點是速度快,且支援變化後立馬自更新 private abstract class CachedValue<T> { // 這三個屬性均是volatile的 private volatile boolean isCached; private volatile IllegalArgumentException exception; private volatile T value; // 獲取值方法:對外public public T getValue() throws IllegalArgumentException { // 若還沒快取過,那就加鎖去快取 if (!isCached) { synchronized (lock) { ... //快取的時候有個parse(stringValue);數據轉換動作 // 當然也有可能拋出異常 parse(stringValue); ... } } else { ... // 返回快取的值:成員屬性value值 } } public T getValue(T defaultValue) { ... } // 數據轉換的抽象方法,子類自行實現 protected abstract T parse(String rep) throws Exception; } // 請記住這兩個數組:很多簡寫都表示true哦,這基本是業界規範 private static final String[] TRUE_VALUES = { "true", "t", "yes", "y", "on" }; private static final String[] FALSE_VALUES = { "false", "f", "no", "n", "off" }; private CachedValue<Boolean> booleanValue = rep -> ...; private CachedValue<Long> longValue = rep -> Long.valueOf(rep); private CachedValue<String> cachedStringValue = ... ... private CachedValue<Class> classValue= rep -> Class.forName(rep); // 有了這種快取值,下面獲取就方便了 public String getString() { return cachedStringValue.getValue(); } ... public Class getNamedClass() throws IllegalArgumentException { return classValue.getValue(); } public <T> Optional<T> getCachedValue(Class<T> objectType) { ... } // 單例設計:獲取一個DynamicProperty 實例,才能拿到具體的value值嘛 // 說明:這個過程的執行緒安全性是由`ConcurrentHashMap`去保證的 public static DynamicProperty getInstance(String propName) { // 空調用:確保配置源Configuration已經註冊上了DynamicPropertyListener監聽 if (dynamicPropertySupportImpl == null) { DynamicPropertyFactory.getInstance(); } // 先從快取拿,拿不到就臨死構造一個放進快取里 DynamicProperty prop = ALL_PROPS.get(propName); if (prop == null) { prop = new DynamicProperty(propName); DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop); if (oldProp != null) { prop = oldProp; } } return prop; } private DynamicProperty(String propName) { this.propName = propName; updateValue(); } }
能被快取的值只有如上類型,當然String類型可以代表任何值,所以它還是通用的。但你是否發現,到目前而止你還不知道如何初始化一個DynamicProperty
,也就是給其屬性如:propName/stringValue
等等賦值,下面就介紹下它的初始化相關相關方法:
DynamicProperty: // 唯一初始化方法:初始化一個DynamicProperty並且給其配置好DynamicPropertyListener監聽器 // 從而具有快取+動態的功能。初始化的時候會updateAllProperties() // 特點:此方法非public static synchronized void initialize(DynamicPropertySupport config) { dynamicPropertySupportImpl = config; config.addConfigurationListener(new DynamicPropertyListener()); updateAllProperties(); } // 該方法含義同上 static void registerWithDynamicPropertySupport(DynamicPropertySupport config) { initialize(config); } // 返回值true:代表值有變化(只要有一個有變換就返回true) private static boolean updateAllProperties() { boolean changed = false; for (DynamicProperty prop : ALL_PROPS.values()) { // 更新值來自於:dynamicPropertySupportImpl.getString(propName); // 其實也就是Configuration文件里嘍 if (prop.updateValue()) { prop.notifyCallbacks(); // 若有變化,就觸發綁定在該值上的所有回調 changed = true; } } return changed; }
這樣子就完成了整個屬性值的初始化:把Configuration
裡面所有的實現都初始化為DynamicProperty
然後快取在全局static變數的Map裡面,這樣子訪問速度變快了並且還天然具有動態的能力了。
但是,initialize
方法它並非public
的,它唯一被(間接)調用處是在DynamicPropertyFactory#initWithConfigurationSource()
這個public方法里,這個類文下也會有詳解。
DynamicPropertyListener
它是DynamicProperty
的一個靜態內部類,用於監聽 DynamicProperty
持有的DynamicPropertySupport
,從而可以完成熟悉發生改變時(新增、修改、clear等),執行對應的操作。
DynamicProperty: // 初始化DynamicProperty時候完成監聽器的註冊 static synchronized void initialize(DynamicPropertySupport config) { dynamicPropertySupportImpl = config; config.addConfigurationListener(new DynamicPropertyListener()); updateAllProperties(); }
static class DynamicPropertyListener implements PropertyListener { // source剛被載入,就可以update所有 @Override public void configSourceLoaded(Object source) { updateAllProperties(); } // 添加屬性時,做相應處理 @Override public void addProperty(Object source, String name, Object value, boolean beforeUpdate) { // 成功後 if (!beforeUpdate) { updateProperty(name, value); } else { // 成功前,先校驗 validate(name, value); } } // 修改一個值時,邏輯完全同上 public void setProperty( ... ){ ... } // 成功後,updateProperty(name, value);即可 public void clearProperty( ... ){ ... } // 清空所有屬性時候觸發的動作(成功後) @Override public void clear(Object source, boolean beforeUpdate) { if (!beforeUpdate) { updateAllProperties(); } } }
DynamicProperty
的動態性是通過此監聽器器來實現的:當org.apache.commons.configuration.Configuration
屬性發生變化時,該監聽器對應方法就會被觸發。
使用示例
因DynamicProperty
並不能直接構建,因此它的使用示例請參見DynamicPropertyFactory
。
DynamicPropertyFactory
顧名思義,該類表示DynamicProperty
動態屬性的工廠。創建動態屬性實例並將其與底層配置或動態屬性支援關聯的工廠,在運行時可以動態更改這些屬性。
該工廠以單例形式使用,單例的初始化邏輯還蠻有看頭的:
public class DynamicPropertyFactory { private static DynamicPropertyFactory instance = new DynamicPropertyFactory(); private DynamicPropertyFactory() {} // 依賴於下面方法,這是內部幫你把AbstractConfiguration適配為了DynamicPropertySupport public static DynamicPropertyFactory initWithConfigurationSource(AbstractConfiguration config) { ... } // 這個方法也是返回一個工廠的實例:但它要求你必須傳入你的DynamicPropertySupport實現類嘍(而不能用系統默認的) public static DynamicPropertyFactory initWithConfigurationSource(DynamicPropertySupport dynamicPropertySupport) { synchronized (ConfigurationManager.class) { if (dynamicPropertySupport == null) { // 必傳 throw new IllegalArgumentException("dynamicPropertySupport is null"); } // 很明顯:這裡大概率是ConfigurationBackedDynamicPropertySupportImpl AbstractConfiguration configuration = null; if (dynamicPropertySupport instanceof AbstractConfiguration) { configuration = (AbstractConfiguration) dynamicPropertySupport; } else if (dynamicPropertySupport instanceof ConfigurationBackedDynamicPropertySupportImpl) { configuration = ((ConfigurationBackedDynamicPropertySupportImpl) dynamicPropertySupport).getConfiguration(); } ... // 忽略部分校驗 // 這個setDirect有意思了:就是給ConfigurationManager管理的直接設置一些值 // 值來源於:AbstractConfiguration,比如instance、監聽器等等 if (configuration != null && configuration != ConfigurationManager.instance) { ConfigurationManager.setDirect(configuration); } // 給本類本廠的本類的屬性賦值 setDirect(dynamicPropertySupport); return instance; } } static void setDirect(DynamicPropertySupport support) { synchronized (ConfigurationManager.class) { config = support; // 這就是對DynamicProperty進行init初始化動作嘍 // configuration裡面所有的屬性都會變為動態的,然後快取著 DynamicProperty.registerWithDynamicPropertySupport(support); initializedWithDefaultConfig = false; } } // 一般我們會使用它:因為它默認幫我們構建new ConfigurationBackedDynamicPropertySupportImpl(config)作為實現 // 不用操心。而使用者只需關心Configuration配置源即可 // 也就是ConfigurationManager.getConfigInstance()這個邏輯 public static DynamicPropertyFactory getInstance() { ... // 邏輯同上的initWithConfigurationSource return instance; } }
該工廠能夠屏蔽DynamicProperty
的初始化以及使用邏輯,讓調用者只需關心配置源Configuration
即可。當然,它還有一些其它幫助方法:
DynamicPropertyFactory: // 這個值:只有Configuration是根據你配置的系統屬性來初始化的才是true private volatile static boolean initializedWithDefaultConfig = false; // 默認情況下:是不會開啟JMX支援的哦 public static final String ENABLE_JMX = "archaius.dynamicPropertyFactory.registerConfigWithJMX"; // 獲取支援的配置源 // 你可以基於Apache Commons Configuration實現,但不是必須的。所以這裡返回的是Object public static Object getBackingConfigurationSource() { if (config instanceof ConfigurationBackedDynamicPropertySupportImpl) { return ((ConfigurationBackedDynamicPropertySupportImpl) config).getConfiguration(); } else { return config; } } // 返回一個DynamicStringProperty,內部原理實際為DynamicProperty public DynamicStringProperty getStringProperty(String propName, String defaultValue) { return getStringProperty(propName, defaultValue, null); } public DynamicStringProperty getStringProperty(String propName, String defaultValue, final Runnable propertyChangeCallback) { checkAndWarn(propName); DynamicStringProperty property = new DynamicStringProperty(propName, defaultValue); addCallback(propertyChangeCallback, property); return property; } ... // 省略getInt、getLong、getDouble等等 // 獲取一個帶上下文的動態屬性 public <T> DynamicContextualProperty<T> getContextualProperty(String propName, T defaultValue) { ... }
從DynamicPropertyFactory
的設計可以看到,它是面向使用者的API,希望屏蔽掉DynamicProperty
的實現細節,使得對調用者更加友好。
使用示例
@Test public void fun2() throws InterruptedException { DynamicPropertyFactory propertyFactory = DynamicPropertyFactory.getInstance(); DynamicStringProperty nameProperty = propertyFactory.getStringProperty("name", "defaultName"); nameProperty.addCallback(() -> System.out.println("name屬性值發生變化:")); // 10秒鐘讀一次 while (true) { System.out.println(nameProperty.get()); TimeUnit.SECONDS.sleep(50); } }
運行程式控制台輸出:
YourBatman name屬性值發生變化: YourBatman-changed
這就是動態屬性的特點:你只需要更改你配置文件的值即可。
注意:
DynamicProperty
默認是1分鐘重載一次哦,具體可參照DynamicURLConfiguration
這個類
和System性能比較: 其實就是Hashtable
和ConcurrentHashMap
+ cache的性能比較,很顯然後者佔優,有興趣者可自行嘗試。