[享學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性能比較: 其實就是HashtableConcurrentHashMap + cache的性能比較,很顯然後者佔優,有興趣者可自行嘗試。