[享学Netflix] 十二、Archaius动态属性DynamicProperty原理详解(重要)
- 2020 年 3 月 18 日
- 筆記
要相信:使用这些类库,你的代码将变得更好。 代码下载地址:https://github.com/f641385712/netflix-learning
目录
前言
上篇文章了解到了Netflix Archaius
它提供的两个支持类:配置管理器ConfigurationManager
和动态属性支持DynamicPropertySupport
。特别是DynamicPropertySupport
它提供了对动态属性的支持,原理便是通过PropertyListener
来完成。
本文将深入了解Netflix Archaius
动态属性相关内容,它用DynamicProperty
表示一个动态k-v属性值,再结合DynamicPropertySupport
提供的监听支持,最终实现属性动态化。
正文
DynamicProperty
:动态属性,一个实例对象代表一个k-v,然后具有动态的能力 DynamicPropertyFactory
:用于构建一个动态属性实例,屏蔽掉DynamicProperty
具体实现,对使用者友好。
DynamicProperty
一个带有缓存的动态配置属性,它具有动态性:当属性变更时会自动更新其缓存。该对象是线程安全的,并且访问速度非常快,高于System.getProperty()
。
说明:效率高于System得益于
ConcurrentHashMap
的效率高于Properties
(Hashtable)
使用场景:该类用于多次获取属性值,并且该值可能会动态更改的情况。如果属性只读一次,则应使用“普通”访问方法。如果属性值是固定的,请考虑将该值缓存在变量中(用变量表示)。
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
等等赋值,下面就介绍下它的初始化相关相关方法:
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等),执行对应的操作。
DynamicProperty: // 初始化DynamicProperty时候完成监听器的注册 static synchronized void initialize(DynamicPropertySupport config) { dynamicPropertySupportImpl = config; config.addConfigurationListener(new DynamicPropertyListener()); updateAllProperties(); }
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
动态属性的工厂。创建动态属性实例并将其与底层配置或动态属性支持关联的工厂,在运行时可以动态更改这些属性。
该工厂以单例形式使用,单例的初始化逻辑还蛮有看头的:
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
即可。当然,它还有一些其它帮助方法:
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
的实现细节,使得对调用者更加友好。
使用示例
@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); } }
运行程序控制台输出:
YourBatman name属性值发生变化: YourBatman-changed
这就是动态属性的特点:你只需要更改你配置文件的值即可。
注意:
DynamicProperty
默认是1分钟重载一次哦,具体可参照DynamicURLConfiguration
这个类
和System性能比较: 其实就是Hashtable
和ConcurrentHashMap
+ cache的性能比较,很显然后者占优,有兴趣者可自行尝试。