前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[享学Netflix] 十二、Archaius动态属性DynamicProperty原理详解(重要)

[享学Netflix] 十二、Archaius动态属性DynamicProperty原理详解(重要)

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

要相信:使用这些类库,你的代码将变得更好。 代码下载地址:https://github.com/f641385712/netflix-learning

目录
  • 前言
  • 正文
    • DynamicProperty
      • DynamicPropertyListener
      • 使用示例
    • DynamicPropertyFactory
      • 使用示例
  • 总结
    • 声明

前言

上篇文章了解到了Netflix Archaius它提供的两个支持类:配置管理器ConfigurationManager和动态属性支持DynamicPropertySupport。特别是DynamicPropertySupport它提供了对动态属性的支持,原理便是通过PropertyListener来完成。

本文将深入了解Netflix Archaius动态属性相关内容,它用DynamicProperty表示一个动态k-v属性值,再结合DynamicPropertySupport提供的监听支持,最终实现属性动态化。


正文

DynamicProperty:动态属性,一个实例对象代表一个k-v,然后具有动态的能力 DynamicPropertyFactory:用于构建一个动态属性实例,屏蔽掉DynamicProperty具体实现,对使用者友好。


DynamicProperty

一个带有缓存的动态配置属性,它具有动态性:当属性变更时会自动更新其缓存。该对象是线程安全的,并且访问速度非常快,高于System.getProperty()

说明:效率高于System得益于ConcurrentHashMap的效率高于Properties(Hashtable)

使用场景:该类用于多次获取属性值,并且该值可能会动态更改的情况。如果属性只读一次,则应使用“普通”访问方法。如果属性值是固定的,请考虑将该值缓存在变量中(用变量表示)。

代码语言:javascript
复制
public class DynamicProperty {
	private volatile static DynamicPropertySupport dynamicPropertySupportImpl;

	// 这就是它速度快的原因。注意是static的哟,需要放置内存泄漏
	private static final ConcurrentHashMap<String, DynamicProperty> ALL_PROPS = new ConcurrentHashMap<>();

	...
    private String propName; // 属性名
    private String stringValue = null; // 属性值
    private long changedTime;
    private CopyOnWriteArraySet<Runnable> callbacks = new CopyOnWriteArraySet<>();
    ...

	// 重要抽象:特定类型的缓存Value
	// 它的特点是速度快,且支持变化后立马自更新
	private abstract class CachedValue<T> {

		// 这三个属性均是volatile的
        private volatile boolean isCached;
        private volatile IllegalArgumentException exception;
        private volatile T value;

		// 获取值方法:对外public
		public T getValue() throws IllegalArgumentException {
			// 若还没缓存过,那就加锁去缓存
			if (!isCached) {
				synchronized (lock) {
					...
					//缓存的时候有个parse(stringValue);数据转换动作
					// 当然也有可能抛出异常
					parse(stringValue);
					... 
				}
			} else {
				... // 返回缓存的值:成员属性value值
			}
			
		}
		public T getValue(T defaultValue) { ... }
		// 数据转换的抽象方法,子类自行实现
		protected abstract T parse(String rep) throws Exception;
	}
	
	// 请记住这两个数组:很多简写都表示true哦,这基本是业界规范
    private static final String[] TRUE_VALUES =  { "true",  "t", "yes", "y", "on"  };
    private static final String[] FALSE_VALUES = { "false", "f", "no",  "n", "off" };
    private CachedValue<Boolean> booleanValue = rep -> ...;
    private CachedValue<Long> longValue = rep -> Long.valueOf(rep);
    private CachedValue<String> cachedStringValue = ...
    ... 
    private CachedValue<Class> classValue= rep -> Class.forName(rep);
    
    // 有了这种缓存值,下面获取就方便了
    public String getString() {
        return cachedStringValue.getValue();
    }
    ...
    public Class getNamedClass() throws IllegalArgumentException {
        return classValue.getValue();
    }
    public <T> Optional<T> getCachedValue(Class<T> objectType) { ... }


	// 单例设计:获取一个DynamicProperty 实例,才能拿到具体的value值嘛
	// 说明:这个过程的线程安全性是由`ConcurrentHashMap`去保证的
    public static DynamicProperty getInstance(String propName) {
		// 空调用:确保配置源Configuration已经注册上了DynamicPropertyListener监听
        if (dynamicPropertySupportImpl == null) {
            DynamicPropertyFactory.getInstance();
        }	
	
		// 先从缓存拿,拿不到就临死构造一个放进缓存里
        DynamicProperty prop = ALL_PROPS.get(propName);
        if (prop == null) {
            prop = new DynamicProperty(propName);
            DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop);
            if (oldProp != null) {
                prop = oldProp;
            }
        }
        return prop;
    }


    private DynamicProperty(String propName) {
        this.propName = propName;
        updateValue();
    }
}

能被缓存的值只有如上类型,当然String类型可以代表任何值,所以它还是通用的。但你是否发现,到目前而止你还不知道如何初始化一个DynamicProperty,也就是给其属性如:propName/stringValue等等赋值,下面就介绍下它的初始化相关相关方法:

代码语言:javascript
复制
DynamicProperty:

	// 唯一初始化方法:初始化一个DynamicProperty并且给其配置好DynamicPropertyListener监听器
	// 从而具有缓存+动态的功能。初始化的时候会updateAllProperties()
	// 特点:此方法非public
    static synchronized void initialize(DynamicPropertySupport config) {
        dynamicPropertySupportImpl = config;
        config.addConfigurationListener(new DynamicPropertyListener());
        updateAllProperties();
    }
    // 该方法含义同上
    static void registerWithDynamicPropertySupport(DynamicPropertySupport config) {
        initialize(config);
    }


    // 返回值true:代表值有变化(只要有一个有变换就返回true)
    private static boolean updateAllProperties() {
        boolean changed = false;
        for (DynamicProperty prop : ALL_PROPS.values()) {

			// 更新值来自于:dynamicPropertySupportImpl.getString(propName);
			// 其实也就是Configuration文件里喽
            if (prop.updateValue()) {
                prop.notifyCallbacks(); // 若有变化,就触发绑定在该值上的所有回调
                changed = true;
            }
        }
        return changed;
    }

这样子就完成了整个属性值的初始化:把Configuration里面所有的实现都初始化为DynamicProperty然后缓存在全局static变量的Map里面,这样子访问速度变快了并且还天然具有动态的能力了。

但是,initialize方法它并非public的,它唯一被(间接)调用处是在DynamicPropertyFactory#initWithConfigurationSource()这个public方法里,这个类文下也会有详解。


DynamicPropertyListener

它是DynamicProperty的一个静态内部类,用于监听 DynamicProperty持有的DynamicPropertySupport,从而可以完成熟悉发生改变时(新增、修改、clear等),执行对应的操作。

代码语言:javascript
复制
DynamicProperty:

	// 初始化DynamicProperty时候完成监听器的注册
    static synchronized void initialize(DynamicPropertySupport config) {
        dynamicPropertySupportImpl = config;
        config.addConfigurationListener(new DynamicPropertyListener());
        updateAllProperties();
    }
代码语言:javascript
复制
static class DynamicPropertyListener implements PropertyListener {

		// source刚被加载,就可以update所有
        @Override
        public void configSourceLoaded(Object source) {
            updateAllProperties();
        }

		// 添加属性时,做相应处理
        @Override
        public void addProperty(Object source, String name, Object value, boolean beforeUpdate) {
        	// 成功后
            if (!beforeUpdate) {
                updateProperty(name, value);
            } else { // 成功前,先校验
                validate(name, value);
            }
        }
        // 修改一个值时,逻辑完全同上
        public void setProperty( ... ){ ... }
        // 成功后,updateProperty(name, value);即可
        public void clearProperty( ... ){ ... }

		// 清空所有属性时候触发的动作(成功后)
        @Override
        public void clear(Object source, boolean beforeUpdate) {
            if (!beforeUpdate) {
                updateAllProperties();
            }
        }
}

DynamicProperty的动态性是通过此监听器器来实现的:当org.apache.commons.configuration.Configuration属性发生变化时,该监听器对应方法就会被触发。


使用示例

DynamicProperty并不能直接构建,因此它的使用示例请参见DynamicPropertyFactory


DynamicPropertyFactory

顾名思义,该类表示DynamicProperty动态属性的工厂。创建动态属性实例并将其与底层配置或动态属性支持关联的工厂,在运行时可以动态更改这些属性

该工厂以单例形式使用,单例的初始化逻辑还蛮有看头的:

代码语言:javascript
复制
public class DynamicPropertyFactory {

	private static DynamicPropertyFactory instance = new DynamicPropertyFactory();
    private DynamicPropertyFactory() {}

	// 依赖于下面方法,这是内部帮你把AbstractConfiguration适配为了DynamicPropertySupport
	public static DynamicPropertyFactory initWithConfigurationSource(AbstractConfiguration config) { ... }
	// 这个方法也是返回一个工厂的实例:但它要求你必须传入你的DynamicPropertySupport实现类喽(而不能用系统默认的)
    public static DynamicPropertyFactory initWithConfigurationSource(DynamicPropertySupport dynamicPropertySupport) {
        synchronized (ConfigurationManager.class) {
            if (dynamicPropertySupport == null) { // 必传
                throw new IllegalArgumentException("dynamicPropertySupport is null");
            }

			// 很明显:这里大概率是ConfigurationBackedDynamicPropertySupportImpl
            AbstractConfiguration configuration = null;
            if (dynamicPropertySupport instanceof AbstractConfiguration) {
                configuration = (AbstractConfiguration) dynamicPropertySupport;
            } else if (dynamicPropertySupport instanceof ConfigurationBackedDynamicPropertySupportImpl) {
                configuration = ((ConfigurationBackedDynamicPropertySupportImpl) dynamicPropertySupport).getConfiguration();
            }
			... // 忽略部分校验
           
			// 这个setDirect有意思了:就是给ConfigurationManager管理的直接设置一些值
			// 值来源于:AbstractConfiguration,比如instance、监听器等等
            if (configuration != null && configuration != ConfigurationManager.instance) {
                ConfigurationManager.setDirect(configuration);
            }
            // 给本类本厂的本类的属性赋值
            setDirect(dynamicPropertySupport);
            return instance;
        }
    }
    static void setDirect(DynamicPropertySupport support) {
        synchronized (ConfigurationManager.class) {
            config = support;
            // 这就是对DynamicProperty进行init初始化动作喽
            // configuration里面所有的属性都会变为动态的,然后缓存着
            DynamicProperty.registerWithDynamicPropertySupport(support);
            initializedWithDefaultConfig = false;
        }
    }


	
	// 一般我们会使用它:因为它默认帮我们构建new ConfigurationBackedDynamicPropertySupportImpl(config)作为实现
	// 不用操心。而使用者只需关心Configuration配置源即可
	// 也就是ConfigurationManager.getConfigInstance()这个逻辑
    public static DynamicPropertyFactory getInstance() {
        ... // 逻辑同上的initWithConfigurationSource
        return instance;
    }
}

该工厂能够屏蔽DynamicProperty的初始化以及使用逻辑,让调用者只需关心配置源Configuration即可。当然,它还有一些其它帮助方法:

代码语言:javascript
复制
DynamicPropertyFactory:

	// 这个值:只有Configuration是根据你配置的系统属性来初始化的才是true
	private volatile static boolean initializedWithDefaultConfig = false;
	
	// 默认情况下:是不会开启JMX支持的哦
	public static final String ENABLE_JMX = "archaius.dynamicPropertyFactory.registerConfigWithJMX";

	// 获取支持的配置源
	// 你可以基于Apache Commons Configuration实现,但不是必须的。所以这里返回的是Object
    public static Object getBackingConfigurationSource() {
        if (config instanceof ConfigurationBackedDynamicPropertySupportImpl) {
            return ((ConfigurationBackedDynamicPropertySupportImpl) config).getConfiguration();
        } else {
            return config;
        }
    }

	// 返回一个DynamicStringProperty,内部原理实际为DynamicProperty
    public DynamicStringProperty getStringProperty(String propName, String defaultValue) {
        return getStringProperty(propName, defaultValue, null);
    }
    public DynamicStringProperty getStringProperty(String propName, String defaultValue, final Runnable propertyChangeCallback) {
        checkAndWarn(propName);
        DynamicStringProperty property = new DynamicStringProperty(propName, defaultValue);
        addCallback(propertyChangeCallback, property);
        return property;
    }
    ... // 省略getInt、getLong、getDouble等等
    
    // 获取一个带上下文的动态属性
    public <T> DynamicContextualProperty<T> getContextualProperty(String propName, T defaultValue) { ... }

DynamicPropertyFactory的设计可以看到,它是面向使用者的API,希望屏蔽掉DynamicProperty的实现细节,使得对调用者更加友好。


使用示例
代码语言:javascript
复制
@Test
public void fun2() throws InterruptedException {
    DynamicPropertyFactory propertyFactory = DynamicPropertyFactory.getInstance();
    DynamicStringProperty nameProperty = propertyFactory.getStringProperty("name", "defaultName");
    nameProperty.addCallback(() -> System.out.println("name属性值发生变化:"));

    // 10秒钟读一次
    while (true) {
        System.out.println(nameProperty.get());
        TimeUnit.SECONDS.sleep(50);
    }
}

运行程序控制台输出:

代码语言:javascript
复制
YourBatman
name属性值发生变化:
YourBatman-changed

这就是动态属性的特点:你只需要更改你配置文件的值即可。

注意:DynamicProperty默认是1分钟重载一次哦,具体可参照DynamicURLConfiguration这个类

和System性能比较: 其实就是HashtableConcurrentHashMap + cache的性能比较,很显然后者占优,有兴趣者可自行尝试。

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

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

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

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

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