要相信:使用这些类库,你的代码将变得更好。 代码下载地址:https://github.com/f641385712/netflix-learning
上篇文章了解到了Netflix Archaius
它提供的两个支持类:配置管理器ConfigurationManager
和动态属性支持DynamicPropertySupport
。特别是DynamicPropertySupport
它提供了对动态属性的支持,原理便是通过PropertyListener
来完成。
本文将深入了解Netflix Archaius
动态属性相关内容,它用DynamicProperty
表示一个动态k-v属性值,再结合DynamicPropertySupport
提供的监听支持,最终实现属性动态化。
DynamicProperty
:动态属性,一个实例对象代表一个k-v,然后具有动态的能力
DynamicPropertyFactory
:用于构建一个动态属性实例,屏蔽掉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方法里,这个类文下也会有详解。
它是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
。
顾名思义,该类表示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的性能比较,很显然后者占优,有兴趣者可自行尝试。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有