[享学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);  }

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