专栏首页BAT的乌托邦[享学Netflix] 十三、Archaius属性抽象Property和PropertyWrapper详解

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

当你去了解一门技术的时候,也确实要去了解它背后的原理,把它融会贯通。这样再学习新的技术才能举一反三,否则每年都会有新的技术潮流,如果不从背后思想上掌握,重新学都是很幸苦的。 代码下载地址: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它和部署环境、上下文有关,动态的进行值的获取,在云计算领域有很强的实用性,因此把它放在下篇文章专文讲解。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • [享学Eureka] 三、Eureka配置之:EurekaInstanceConfig实例配置

    大家对Spring Cloud技术体系的使用应该有个感受:配置太多了,真的是多如牛毛啊。这是实话且是现状,因此坊间笑言:现在很多架构师为“配置工程师”或许更为恰...

    YourBatman
  • 【小家Spring】Spring Framework提供的实用纯Java工具类大合集(一)

    在Spring Framework里的spring-core核心包里面,有个org.springframework.util里面有不少非常实用的工具类。

    YourBatman
  • 【小家java】java8新特性之---方法引用

    简单地说,就是一个Lambda表达式。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的...

    YourBatman
  • JMail接收发送邮件使用参考

    用户2135432
  • 如何更规范的写Java代码

    如何更规范化编写Java 代码的重要性想必毋需多言,其中最重要的几点当属提高代码性能、使代码远离Bug、令代码更优雅。

    田维常
  • 老板看了我的代码,直呼“666”,要涨工资?

    背景:如何更规范化编写Java 代码的重要性想必毋需多言,其中最重要的几点当属提高代码性能、使代码远离Bug、令代码更优雅。

    搜云库技术团队
  • 深入理解String类

    String是java中的字符串。String类是不可变的,对String类的任何改变,都是返回一个新的String类对象。String不属于8种基本数据类型,...

    栋先生
  • 这样规范写代码,同事直呼“666”

    乔戈里
  • 关于SpringMVC中如何把查询数据全转成String类型

    上帝
  • 武汉疫情系列(工具类)|JAVA爬取丁香医生|腾讯新闻|新浪等全国新型肺炎疫情实时动态

    小小鱼儿小小林

扫码关注云+社区

领取腾讯云代金券