当你去了解一门技术的时候,也确实要去了解它背后的原理,把它融会贯通。这样再学习新的技术才能举一反三,否则每年都会有新的技术潮流,如果不从背后思想上掌握,重新学都是很幸苦的。 代码下载地址: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
,这是它最基础的常识。
它是个接口,是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
为其最为重要的一个继承分支。
它作为一个最重要的分支,是因为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
里,这才能保证当属性修改时自己能感知到CopyOnWriteArraySet
的性能开销,没必要 所以,需要添加进SUBCLASSES_WITH_NO_CALLBACK
来的的原则是你知道自己没有复写其任意一个方法时,添加进来可以提高效率,但是即使你忘了没有添加进来(那就会帮你注册回调),逻辑上也不会有任何错误,只是效率低那么一丢丢而已,这就是一个良好的容错设计思想。
给PropertyWrapper
添加callback回调等方法,最终都是作用到了DynamicProperty
里,用于监听它的相关属性操作方法,所以我说它才是背后的操盘者。
PropertyWrapper<V>
自己是抽象类,它拥有众多子类实现:
父类做的事情已经很多,留给子类的事情不多了(基本只需确定类型即可)。
值为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
。
每当原始值被更改时,该实现都会缓存它(使用局部变量缓存)。
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...
等等原理一毛一样,就不用再展开了。
这些子类是非常简单且常用的,下面来两个高级货。
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
也提供了对应的属性支持。
通过一个正则表达式(默认是通过逗号分隔),把一个字符串转为一个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
的实际逻辑是交给子类去实现的~
从命名就能知道,它的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
它和部署环境、上下文有关,动态的进行值的获取,在云计算领域有很强的实用性,因此把它放在下篇文章专文讲解。