[享學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)
。鑒於此,這裡著重解釋一下這句程式碼的緣由:
- 首先需要明白:本類的
propertyChanged();
和validate(newValue)
兩個方法均為空實現 - 按照道理,本來這兩個方法均得註冊進
DynamicProperty
里,這才能保證當屬性修改時自己能感知到 - 而第1步說了,它們是空實現,所以其實註冊不註冊是無所謂的。按照官方的說法,註冊空實現上去反倒有
CopyOnWriteArraySet
的性能開銷,沒必要- 這一點是官方做法的核心考量,為性能提升優化到極致
- 但是,但是,但是 ,子類很有可能會複寫這些方法而實現自己的邏輯,若你此處不註冊就不會觸發回調,從而帶來可能的邏輯上的錯誤,所以就來了這麼一個判斷。
- 這個判斷是個小細節,用的逆向思維:若你不需要註冊回調,那就把你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
給你使用,一般使用較少~
使用示例
說明:重點介紹
DynamicListProperty
和DynamicMapProperty
在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
它和部署環境、上下文有關,動態的進行值的獲取,在雲計算領域有很強的實用性,因此把它放在下篇文章專文講解。