[享学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); }
简单说,就是借助谷歌的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
它和部署环境、上下文有关,动态的进行值的获取,在云计算领域有很强的实用性,因此把它放在下篇文章专文讲解。