[享學Netflix] 十三、Archaius屬性抽象Property和PropertyWrapper詳解

  • 2020 年 3 月 18 日
  • 筆記

當你去了解一門技術的時候,也確實要去了解它背後的原理,把它融會貫通。這樣再學習新的技術才能舉一反三,否則每年都會有新的技術潮流,如果不從背後思想上掌握,重新學都是很幸苦的。 程式碼下載地址:https://github.com/f641385712/netflix-learning

目錄

前言

上篇文章介紹了Archaius動態屬性DynamicProperty,並且通過DynamicPropertyFactory間接的體驗了一把它天生的動態性。

在案例過程中,你可能已經發現,操作屬性時實際與使用者打交道的並不是DynamicProperty或者其子類,而是諸如DynamicIntProperty、DynamicStringProperty這類API。那麼本文將一起進入Netflix Archaius的屬性抽象:com.netflix.config.Property,全面了解它到底是如何完成從配置文件到Java Property屬性的。


正文

Netflix Archaius所有的的屬性都抽象為了一個com.netflix.config.Property用於表示,因此想要真了解Netflix Archaius,這是它最基礎的常識。


Property

它是個介面,是Archaius的基本介面,表示一個屬性。提供訪問、操作此屬性的基本方法:

// 泛型T表示屬性值value的type類型  public interface Property<T> {    	// 獲取屬性值  	T getValue();  	T getDefaultValue();  	// 獲取屬性名  	String getName();    	// 當屬性值被change或者set的時候,此值會變  	long getChangedTimestamp();    	// 當屬性值改變時,此回調會被觸發  	void addCallback(Runnable callback);  	void removeAllCallbacks();  }

從介面可以知道,對於一個屬性,不僅有屬性名、屬性值、默認值等,還能為屬性註冊回調,這樣當屬性發生修改時就能觸發對應的回調動作,實現對此屬性的監控效果。 該介面實現類眾多:

其中PropertyWrapper為其最為重要的一個繼承分支。


PropertyWrapper

它作為一個最重要的分支,是因為Property所有實現都直接/間接的依賴於它。 PropertyWrapper屬性包裝器,並將其與類型關聯,內含一個DynamicProperty的引用,它是實際幹事的(因此上文的知識鋪層很重要,環環相扣)。

public abstract class PropertyWrapper<V> implements Property<V> {    	// DynamicProperty它是作為實際的存儲屬性k-v的地方,從而擁有了動態性  	protected final DynamicProperty prop;  	protected final V defaultValue;    	// 不需要回調的子類(為何不需要回調?下文有講)  	private static final IdentityHashMap<Class<? extends PropertyWrapper>, Object> SUBCLASSES_WITH_NO_CALLBACK = new IdentityHashMap<>();  	// 請注意:registerSubClassWithNoCallback是個public的靜態方法  	// 若你有自定義的子類的實現,也可通過此方法註冊進來      static {          PropertyWrapper.registerSubClassWithNoCallback(DynamicIntProperty.class);          PropertyWrapper.registerSubClassWithNoCallback(DynamicStringProperty.class);          PropertyWrapper.registerSubClassWithNoCallback(DynamicBooleanProperty.class);          PropertyWrapper.registerSubClassWithNoCallback(DynamicFloatProperty.class);          PropertyWrapper.registerSubClassWithNoCallback(DynamicLongProperty.class);          PropertyWrapper.registerSubClassWithNoCallback(DynamicDoubleProperty.class);      }  	// 佔位的值,放到上面的Map的value里,畢竟value值沒啥意義(畢竟木有IdentityHashSet嘛)  	private static final Object DUMMY_VALUE = new Object();    	// 回調們,們,們  	private final Set<Runnable> callbacks = new CopyOnWriteArraySet<Runnable>();    	// 唯一構造器:指定key和默認值value  	protected PropertyWrapper(String propName, V defaultValue) {  		// 此句執行的前提是:DynamicProperty已經被init了  		// 就是說是通過DynamicPropertyFactory生成的實例  		this.prop = DynamicProperty.getInstance(propName);  		this.defaultValue = defaultValue;      		// 上為正常的屬性賦值,下面是稍微那麼一丟丟難理解的步驟。    		Class<?> c = getClass();          if (!SUBCLASSES_WITH_NO_CALLBACK.containsKey(c)) {          	// 註冊回調 propertyChanged()是本類實現:但是是空實現              this.prop.addCallback(() -> propertyChanged());              callbacks.add(callback); // 這個callback也添加到全局裡了喲    			// 註冊校驗器 validate():本類空實現              this.prop.addValidator(() -> PropertyWrapper.this.validate(newValue));                // 利用校驗器校驗下value值~~~~              // 注意和上的區別,上面校驗的是newValue              try {                  if (this.prop.getString() != null) {                      this.validate(this.prop.getString());                  }              } catch (ValidationException e) {              	// 若校驗失敗,就使用默認值嘍              	// 比如要int值,你給我傳給aaa,不久校驗失敗了麽~~~                  logger.warn("Error validating property at initialization. Will fallback to default value.", e);                  prop.updateValue(defaultValue);              }          }  	}    	// 這兩個關鍵方法均為空實現      protected void propertyChanged() {          propertyChanged(getValue());      }      protected void propertyChanged(V newValue) {          // by default, do nothing      }      protected void validate(String newValue) {  	    // by default, do nothing      }    	...      @Override      public long getChangedTimestamp() {          return prop.getChangedTimestamp();      }  	...    	// 添加callback 移除等最終都是作用到了`DynamicProperty`里了      @Override      public void addCallback(Runnable callback) {          if (callback != null) {              prop.addCallback(callback);              callbacks.add(callback);          }      }  }

這段源碼,最難理解的一句是:!SUBCLASSES_WITH_NO_CALLBACK.containsKey(c)。鑒於此,這裡著重解釋一下這句程式碼的緣由:

  1. 首先需要明白:本類的propertyChanged();validate(newValue)兩個方法均為空實現
  2. 按照道理,本來這兩個方法均得註冊進DynamicProperty里,這才能保證當屬性修改時自己能感知到
  3. 而第1步說了,它們是空實現,所以其實註冊不註冊是無所謂的。按照官方的說法,註冊空實現上去反倒有CopyOnWriteArraySet的性能開銷,沒必要
    1. 這一點是官方做法的核心考量,為性能提升優化到極致
  4. 但是,但是,但是 ,子類很有可能會複寫這些方法而實現自己的邏輯,若你此處不註冊就不會觸發回調,從而帶來可能的邏輯上的錯誤,所以就來了這麼一個判斷。
    1. 這個判斷是個小細節,用的逆向思維:若你不需要註冊回調,那就把你Class類型添加進來。

所以,需要添加進SUBCLASSES_WITH_NO_CALLBACK來的的原則是你知道自己沒有複寫其任意一個方法時,添加進來可以提高效率,但是即使你忘了沒有添加進來(那就會幫你註冊回調),邏輯上也不會有任何錯誤,只是效率低那麼一丟丟而已,這就是一個良好的容錯設計思想。

PropertyWrapper添加callback回調等方法,最終都是作用到了DynamicProperty里,用於監聽它的相關屬性操作方法,所以我說它才是背後的操盤者。

PropertyWrapper<V>自己是抽象類,它擁有眾多子類實現:

父類做的事情已經很多,留給子類的事情不多了(基本只需確定類型即可)。


DynamicLongProperty

值為Long類型的動態屬性。

public class DynamicLongProperty extends PropertyWrapper<Long> {        public DynamicLongProperty(String propName, long defaultValue) {          super(propName, Long.valueOf(defaultValue));      }    	// get方法其實不是介面方法      public long get() {          return prop.getLong(defaultValue).longValue();      }      // 這個才是抽象方法      @Override      public Long getValue() {          return get();      }    }

小細節:DynamicLongProperty並沒有複寫propertyChanged()等方法,所以你可以看到它被加入到了SUBCLASSES_WITH_NO_CALLBACK


CachedDynamicLongProperty

每當原始值被更改時,該實現都會快取它(使用局部變數快取)。

public class CachedDynamicLongProperty extends DynamicLongProperty {    	protected volatile long primitiveValue;  	...    	// 當屬性發生變化時,更新快取值      @Override      protected void propertyChanged() {          this.primitiveValue = chooseValue();      }      // 注意:longValue()一下的效率是高於自動拆箱的      protected long chooseValue() {          return prop.getLong(defaultValue).longValue();      }    	// 他倆的唯一區別:一個是原始類型,一個是包裝類型  	// 建議使用get()而非getValue()方法      @Override      public long get() {          return primitiveValue;      }      @Override      public Long getValue() {          return get();      }    }

這個子類可以提高性能,是因為可以避免拆箱,但要付出額外的記憶體使用代價(空間換時間)。 另說明一點:因為本類複寫了propertyChanged()方法,所以它就不能添加到SUBCLASSES_WITH_NO_CALLBACK里嘍。

其它子類諸如DynamicBooleanProperty、DynamicDoubleProperty...等等原理一毛一樣,就不用再展開了。


這些子類是非常簡單且常用的,下面來兩個高級貨。

StringDerivedProperty

Derived:衍生的,派生的。可以將字元串類型的屬性值,派生為任意類型~

public class StringDerivedProperty<T> extends PropertyWrapper<T> {    	// 轉換函數:String -> 任意類型  	protected final Function<String, T> decoder;  	// 具有快取作用  	private volatile T derivedValue;    	// 構造器,要求必須把decoder指定嘍      public StringDerivedProperty(String propName, T defaultValue, Function<String, T> decoder) {          super(propName, defaultValue);          this.decoder = decoder;          propertyChangedInternal();      }      ...  }

此類在Archaius內部並沒有被使用到過,因為其實大多數自己拿到String再轉也行,所以用於特殊場景。

另外,StringDerivedProperty並不能通過DynamicPropertyFactory來得到,所以需要你自己通過構造器構造(發生在DynamicProperty持有的DynamicPropertySupport有值了就行~)


關於其實現子類DynamicContextualProperty,和上下文有關的實現,因為過於重要,所以放在下篇文章專門講解,請移步參閱


當想通過屬性配置一個List/Set的時候,Archaius也提供了對應的屬性支援。

DynamicListProperty

通過一個正則表達式(默認是通過逗號分隔),把一個字元串轉為一個List<T>(List裡面裝什麼類型可不定~)

public abstract class DynamicListProperty<T> implements Property<List<T>> {    	private volatile List<T> values;  	private List<T> defaultValues;    	// 從動態屬性里拿到字元串  最終轉換一下即可  	private DynamicStringProperty delegate;  	// 可根據正則表達式拆分字元串  	private Splitter splitter;  	public static final String DEFAULT_DELIMITER = ",";    	// 省略其它構造器  	// delimiterRegex可以是正則。默認使用,分隔  	public DynamicListProperty(String propName, String defaultValue, String delimiterRegex) {  		this.splitter = Splitter.onPattern(delimiterRegex).omitEmptyStrings().trimResults();  		// split:splitter.split(Strings.nullToEmpty(value))  		// transform:from(s) -> 抽象方法,交給子類實現    		// setup:從配置里拿到Strng值:DynamicPropertyFactory.getInstance().getStringProperty(propName, null);  		// 然後做transform轉換動作  		setup(propName, transform(split(defaultValue)), splitter);  	}  	...          public List<T> get() {          return values;      }      @Override      public List<T> getValue() {          return get();      }      ...  	protected abstract T from(String value);  }

簡單說,就是藉助Google的com.google.common.base.Splitter這個API來處理字元串,轉換為List<T>類型。當然,具體的String -> T的實際邏輯是交給子類去實現的~


DynamicStringListProperty

從命名就能知道,它的T是String類型。

public class DynamicStringListProperty extends DynamicListProperty<String> {  	...      @Override      protected String from(String value) {          return value;      }  }

它也有一個子類,並且還是抽象的:DynamicMapProperty

// 確定了value值是String類型,但是又增加了兩個新的泛型<TKEY, TVAL>  public abstract class DynamicMapProperty<TKEY, TVAL> extends DynamicStringListProperty {        private Map<TKEY,TVAL> defaultValuesMap;      private volatile Map<TKEY,TVAL> values;    	protected Map<TKEY,TVAL> parseMapFromStringList(List<String> strings) { ... }    	// 抽象方法      protected abstract TKEY getKey(String key);      protected abstract TVAL getValue(String value);  }

實現類為:DynamicStringMapProperty,也都是String類型:

public class DynamicStringMapProperty extends DynamicMapProperty<String, String> {  	...      @Override      protected String getKey(String key) {          return key;      }      @Override      protected String getValue(String value) {          return value;      }  }

有了它們,給xxx.properties屬性文件的配置,結構上提供了更多可能,下面會給出使用實例感受一把。

當然啦,當你需要去重的時候,還提供了DynamicSetProperty/DynamicStringSetProperty給你使用,一般使用較少~


使用示例

說明:重點介紹DynamicListPropertyDynamicMapProperty

config.properties文件里提供如下內容:

name=YourBatman  age=18    # List  hobbies=basketball,football,pingpong  # Map:k-v之前必須使用=好鏈接  persons=father={"name":"YoutBatman","age":18}#son={"name":"YoutBatman-son","age":2}

測試程式程式碼:

@Test  public void fun4() {      DynamicPropertyFactory factory = DynamicPropertyFactory.getInstance();        // 普通的 -> 直接使用工廠獲取實例即可      DynamicIntProperty ageProperty = factory.getIntProperty("age", 0);      System.out.println(ageProperty.get());      System.out.println("------------------------------------");          // List(set同理)      DynamicStringListProperty hobbiesProperty = new DynamicStringListProperty("hobbies", "");      List<String> hobbies = hobbiesProperty.get();      System.out.println(hobbies);      System.out.println("------------------------------------");        // Map      // 此處使用#號做分隔 是因為JSON串得使用逗號嘍      DynamicStringMapProperty personsProperty = new DynamicStringMapProperty("persons", "", "#");        List<String> personStrs = personsProperty.get();      Map<String, String> map = personsProperty.getMap();      System.out.println(personStrs);      map.forEach((k, v) -> System.out.println(k + "->" + v));      System.out.println("------------------------------------");  }

運行程式,輸出:

18  ------------------------------------  [basketball, football, pingpong]  ------------------------------------  [father={"name":"YoutBatman","age":18}, son={"name":"YoutBatman-son","age":2}]  father->{"name":"YoutBatman","age":18}  son->{"name":"YoutBatman-son","age":2}  ------------------------------------

一般情況下,對於List、Map情況,大多時候希望一步到位得到的就是個POJO,那就需要如下訂製:

// 配置資訊得到Person實例  @Getter  @Setter  @ToString  private static class Person {      private String name;      private Integer age;  }

自定義一個DynamicMapProperty,完成轉換工作:

private static class DynamicPersonMapProperty extends DynamicMapProperty<String, Person> {        private static final ObjectMapper MAPPER = new ObjectMapper();        public DynamicPersonMapProperty(String propName, String defaultValue, String mapEntryDelimiterRegex) {          super(propName, defaultValue, mapEntryDelimiterRegex);      }      @Override      protected String getKey(String key) {          return key;      }      // 反序列化為Person對象      @Override      protected Person getValue(String value) {          try {              return MAPPER.readValue(value, Person.class);          } catch (JsonProcessingException e) {              return null;          }      }    }

測試程式:

    @Test  public void fun5(){      DynamicMapProperty<String, Person> personsProperty = new DynamicPersonMapProperty("persons", "", "#");      Map<String, Person> persons = personsProperty.getMap();      List<String> value = personsProperty.getValue();      System.out.println(value);      System.out.println(persons);  }

輸出:

[father={"name":"YoutBatman","age":18}, son={"name":"YoutBatman-son","age":2}]  {father=Test2.Person(name=YoutBatman, age=18), son=Test2.Person(name=YoutBatman-son, age=2)}

這樣就可以完成配置 -> POJO的映射嘍,使用起來更加方便。


總結

關於Netflix Archaius屬性抽象Property/PropertyWrapper詳解部分就介紹到這了,經過本文了解了整個Property體系的內容,這樣使用起來也不慌了。

另外,其中有個子類DynamicContextualProperty它和部署環境、上下文有關,動態的進行值的獲取,在雲計算領域有很強的實用性,因此把它放在下篇文章專文講解。