前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Netflix] 十三、Archaius属性抽象Property和PropertyWrapper详解

[享学Netflix] 十三、Archaius属性抽象Property和PropertyWrapper详解

作者头像
YourBatman
发布2020-03-18 19:28:29
8850
发布2020-03-18 19:28:29
举报
文章被收录于专栏:BAT的乌托邦BAT的乌托邦

当你去了解一门技术的时候,也确实要去了解它背后的原理,把它融会贯通。这样再学习新的技术才能举一反三,否则每年都会有新的技术潮流,如果不从背后思想上掌握,重新学都是很幸苦的。 代码下载地址:https://github.com/f641385712/netflix-learning

目录
  • 前言
  • 正文
    • Property
      • PropertyWrapper
        • DynamicLongProperty
        • CachedDynamicLongProperty
        • StringDerivedProperty
        • DynamicListProperty
        • DynamicStringListProperty
      • 使用示例
  • 总结
    • 声明

前言

上篇文章介绍了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的基本接口,表示一个属性。提供访问、操作此属性的基本方法:

代码语言:javascript
复制
// 泛型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的引用,它是实际干事的(因此上文的知识铺层很重要,环环相扣)。

代码语言:javascript
复制
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类型的动态属性。

代码语言:javascript
复制
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

每当原始值被更改时,该实现都会缓存它(使用局部变量缓存)。

代码语言:javascript
复制
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:衍生的,派生的。可以将字符串类型的属性值,派生为任意类型~

代码语言:javascript
复制
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里面装什么类型可不定~)

代码语言:javascript
复制
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类型。

代码语言:javascript
复制
public class DynamicStringListProperty extends DynamicListProperty<String> {
	...
    @Override
    protected String from(String value) {
        return value;
    }
}

它也有一个子类,并且还是抽象的:DynamicMapProperty

代码语言:javascript
复制
// 确定了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类型:

代码语言:javascript
复制
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文件里提供如下内容:

代码语言:javascript
复制
name=YourBatman
age=18

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

测试程序代码:

代码语言:javascript
复制
@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("------------------------------------");
}

运行程序,输出:

代码语言:javascript
复制
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,那就需要如下定制:

代码语言:javascript
复制
// 配置信息得到Person实例
@Getter
@Setter
@ToString
private static class Person {
    private String name;
    private Integer age;
}

自定义一个DynamicMapProperty,完成转换工作:

代码语言:javascript
复制
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;
        }
    }

}

测试程序:

代码语言:javascript
复制
    @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);
}

输出:

代码语言:javascript
复制
[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它和部署环境、上下文有关,动态的进行值的获取,在云计算领域有很强的实用性,因此把它放在下篇文章专文讲解。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 目录
  • 前言
  • 正文
    • Property
      • PropertyWrapper
      • 使用示例
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档