要相信:也有很多人比你更勤奋 代码下载地址:https://github.com/f641385712/netflix-learning
上篇文章体验了一把Netflix Archaius的使用,感受到了它对配置管理上的便捷性。或许有小伙伴会说,配置管理上它和Apache Commons Configuration功能上有点重叠,其实不然。
他俩的关系不是功能重叠,而是Netflix Archaius是对Apache Commons Configuration的一种延伸,并且前者依赖于后者的实现。
因此本文将探讨下Netflix Archaius它对Apache Commons configuration的扩展,在API层面做了哪些自定义以及使用上的增强。
org.apache.commons.configuration.Configuration是Apache Commons configuration的核心接口,Netflix Archaius就是依赖了它来实现内部的配置源。
而我们知道Netflix Archaius它是一个高效的,线程安全的配置管理库,因此它必然在Configuration的基础上做了扩展和增强。
在API层面,它最重要的便是在org.apache.commons.configuration.Configuration上提供了扩展。主要分为两个体系:ConcurrentMapConfiguration和AggregatedConfiguration。
该类使用ConcurrentHashMap读取/写入属性以获得高吞吐量和线程安全。其它实现均基于它。
public class ConcurrentMapConfiguration extends AbstractConfiguration {
// Map用来存储属性值,是个ConcurrentHashMap
// 其它集合之类的均是线程安全的~
protected Map<String,Object> map;
private Collection<ConfigurationListener> listeners = new CopyOnWriteArrayList<ConfigurationListener>();
private Collection<ConfigurationErrorListener> errorListeners = new CopyOnWriteArrayList<ConfigurationErrorListener>();
...
// 系统属性。是否禁用分隔符去解析配置属性
// 比如若配置为不禁用,那么1,2,3会被解析为List
public static final String DISABLE_DELIMITER_PARSING = "archaius.configuration.disableDelimiterParsing";
...
@Override
public Object getProperty(String key) {
return map.get(key);
}
...
@Override
public void setProperty(String key, Object value) throws ValidationException {
fireEvent(EVENT_SET_PROPERTY, key, value, true);
// 看看value是否允许解析,等等
// 如果允许解析,那就作为作为List<Object>放进去喽
setPropertyImpl(key, value);
fireEvent(EVENT_SET_PROPERTY, key, value, false);
}
... // 省略注册事件、发送事件等其它方法的实现
}如果熟悉Apache Commons Configuration的使用和源码,此段代码没啥特别的。若你还不熟悉,建议你从此处:[享学Netflix] 一、Apache Commons Configuration:你身边的配置管理专家 开始了解。
它的继承图如下:
动态配置,它会依赖PolledConfigurationSource/AbstractPollingScheduler等来实现配置的动态化
public class DynamicConfiguration extends ConcurrentMapConfiguration {
// 轮询Scheduler
private AbstractPollingScheduler scheduler;
// 配置源
private PolledConfigurationSource source;
// 说明:Commons Configuration的事件类型是通过int值区分的,此处定义一个100
// 表示RELOAD重新加载事件
public static final int EVENT_RELOAD = 100;
// 注意这两个构造器的区别
// 无参构造:需要自己手动调用startPolling()才能启动轮询
// 有参数构造:自动启动轮询
public DynamicConfiguration() {
super();
}
public DynamicConfiguration(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {
this();
startPolling(source, scheduler);
}
// 启动轮询
public synchronized void startPolling(PolledConfigurationSource source, AbstractPollingScheduler scheduler) {
this.scheduler = scheduler;
this.source = source;
// 空方法,钩子方法,交给子类去实现
init(source, scheduler);
// 给Scheduler注册监听器:把事件类型转换为对应的Commons Configuration事件发出去
scheduler.addPollListener(new PollListener() {
@Override
public void handleEvent(EventType eventType, PollResult lastResult, Throwable exception) {
switch (eventType) {
case POLL_SUCCESS:
fireEvent(EVENT_RELOAD, null, null, false);
break;
case POLL_FAILURE:
fireError(EVENT_RELOAD, null, null, exception);
break;
case POLL_BEGIN:
fireEvent(EVENT_RELOAD, null, null, true);
break;
}
}
});
// 启动轮询
scheduler.startPolling(source, this);
}
public synchronized void stopLoading() {
if (scheduler != null) {
scheduler.stop();
}
}
}这是一个自动动态性的org.apache.commons.configuration.Configuration。
它继承自DynamicConfiguration,从名称成也能知道,它使用的配置源是URLConfigurationSource。
public class DynamicURLConfiguration extends DynamicConfiguration {
// URLConfigurationSource再熟悉不过了:默认会加载config.properties等等哦
// 空构造就启动了轮询策略:因为已经制定了使用FixedDelay来轮询(事件参数均为默认)
public DynamicURLConfiguration() {
URLConfigurationSource source = new URLConfigurationSource();
if (source.getConfigUrls() != null && source.getConfigUrls().size() > 0) {
startPolling(source, new FixedDelayPollingScheduler());
}
}
// 当然喽,还有个带参构造
public DynamicURLConfiguration(int initialDelayMillis, int delayMillis, boolean ignoreDeletesFromSource, String... urls) { ... }
}这个子类确定以及肯定了使用URLConfigurationSource和FixedDelayPollingScheduler来完成动态属性轮询,因此使用子类的case比使用父类多。
使用DynamicURLConfiguration可以非常方便的让属性动态化:
@Test
public void fun4() throws InterruptedException {
DynamicURLConfiguration config = new DynamicURLConfiguration();
while (true) {
ConfigurationUtils.dump(config, System.out);
System.out.println();
TimeUnit.SECONDS.sleep(10);
}
}控制台打印:
name=YourBatman
name=YourBatman
name=YourBatman
name=YourBatman-changed
...代码比上篇文章的实现优雅太多了有没有。
说明:默认的轮询时间延迟30秒执行(所以你看到先输出了3个原始值),然后60秒轮询一次
标准的模块化配置方法。
假设您的应用程序使用了许多模块**(.jar文件)和需求属性支持,这个类提供了一个基于约定的方法**:从特定类路径的每个jar中扫描和加载属性位置,这个位置是"META-INF/conf/config.properties"。
public class ClasspathPropertiesConfiguration extends ConcurrentMapConfiguration {
// 默认位置 此值可通过下面set方法修改,但不建议改
static String propertiesResourceRelativePath = "META-INF/conf/config.properties";
public static void setPropertiesResourceRelativePath() { ... }
// 它通过下面的init方法初始化
static ClasspathPropertiesConfiguration instance = null;
public static void initialize() {
try {
instance = new ClasspathPropertiesConfiguration();
// 加载此路径下的config.properties呗(包括jar内的哦)
loadResources(propertiesResourceRelativePath);
} catch (Exception e) {
throw new RuntimeException("failed to read configuration properties from classpath", e);
}
}
// 单例设计
private ClasspathPropertiesConfiguration() {}
...
}该实现是个单例设计,使用它仅需执行一次init操作即可。但是,但是,但是你会发现:它的Instance实例是不能包外访问的,所以我们其实并不能直接使用它。
而它唯一使用地是WebApplicationProperties#init方法,由此可见一般用于Web环境。
一个特殊的配置,它的特点是:更改底层数据源,然后就可以手动去同步器属性(也就是说可以换底层数据源…)。
// WatchedUpdateListener:根据WatchedUpdateResult对Config进行更新
public class DynamicWatchedConfiguration extends ConcurrentMapConfiguration implements WatchedUpdateListener {
// 下面这几个属性都是前面讲过的熟悉属性
// 该接口没有任何实现,该接口提供data,并且可对data进行update/delete
private final WatchedConfigurationSource source;
private final boolean ignoreDeletesFromSource;
private final DynamicPropertyUpdater updater;
...
}private static class MyWatchedConfigurationSource implements WatchedConfigurationSource {
private final Map<String, Object> data;
private final List<WatchedUpdateListener> listeners;
public MyWatchedConfigurationSource(Map<String, Object> data) {
this.data = data;
listeners = new CopyOnWriteArrayList<>();
}
@Override
public void addUpdateListener(WatchedUpdateListener listener) {
listeners.add(listener);
}
@Override
public void removeUpdateListener(WatchedUpdateListener listener) {
listeners.remove(listener);
}
@Override
public Map<String, Object> getCurrentData() throws Exception {
return data;
}
}
@Test
public void fun5() {
Map<String, Object> data = new HashMap<>();
data.put("name", "YourBatman");
DynamicWatchedConfiguration config = new DynamicWatchedConfiguration(new MyWatchedConfigurationSource(data));
ConfigurationUtils.dump(config, System.out);
System.out.println("\n");
// 改变属性,会发现底层的Map也会改
data.put("age", 18);
// 注意:这一步是手动的。。。。
config.updateConfiguration(WatchedUpdateResult.createFull(data));
ConfigurationUtils.dump(config, System.out);
}控制台输出:
name=YourBatman
name=YourBatman
age=18Aggregated:聚合的,合计的。
public interface AggregatedConfiguration extends Configuration {
public void addConfiguration(AbstractConfiguration config);
public void addConfiguration(AbstractConfiguration config, String name);
public Set<String> getConfigurationNames();
public List<String> getConfigurationNameList();
public Configuration getConfiguration(String name);
public int getNumberOfConfigurations();
public Configuration getConfiguration(int index);
public List<AbstractConfiguration> getConfigurations();
public Configuration removeConfiguration(String name);
public boolean removeConfiguration(Configuration config);
public Configuration removeConfigurationAt(int index);
}它有唯一实现类:ConcurrentCompositeConfiguration,同时也是ConcurrentMapConfiguration的子类。这个组合模式很重要,它是Netflix Archaius实现组合模式的核心要义。
此类在列表结构中维护配置的层次结构,要确定属性值时,列表的顺序代表配置的降序优先级。这个规则和Spring的属性源是一样的,所以容易理解~
// 实现ConfigurationListener接口,是可以监听ConfigurationEvent事件
// 注意:这个接口是Apache Commons Configuration的接口
public class ConcurrentCompositeConfiguration extends ConcurrentMapConfiguration implements AggregatedConfiguration, ConfigurationListener, Cloneable {
// 每个配置都给一个name名称,不能重复(特别像Spring有木有)
// 这里Map装的是有名词的,key是所有的具名的
// configList存储的是所有的,不管有名字与否。所以List内容一般多余Map
private Map<String, AbstractConfiguration> namedConfigurations = new ConcurrentHashMap<>();
private List<AbstractConfiguration> configList = new CopyOnWriteArrayList<>();
...
// 事件类型:
public static final int EVENT_CONFIGURATION_SOURCE_CHANGED = 10001;
...
// clear() 清空configList、namedConfigurations
// 然后放置一个默认的容器所属的containerConfiguration
public ConcurrentCompositeConfiguration() {
clear();
}
// 指定容器所属的containerConfiguration
public ConcurrentCompositeConfiguration(AbstractConfiguration containerConfiguration) { ... }
public ConcurrentCompositeConfiguration(AbstractConfiguration containerConfiguration, Collection<? extends AbstractConfiguration> configurations) { ... }
...
public List<AbstractConfiguration> getConfigurations() {
return Collections.unmodifiableList(configList);
}
// 只返回有名字的哪些配置们 的名字们
public List<String> getConfigurationNameList() { ... }
// 在list里的位置,木有就是-1喽
public int getIndexOfConfiguration(AbstractConfiguration config) {
return configList.indexOf(config);
}
public int getNumberOfConfigurations() {
return configList.size();
}
public int getIndexOfContainerConfiguration() {
return configList.indexOf(containerConfiguration);
}
... // 下面是操作属性的接口方法实现
@Override
public void setProperty(String key, Object value) {
containerConfiguration.setProperty(key, value);
}
@Override
public void addProperty(String key, Object value) {
containerConfiguration.addProperty(key, value);
}
...
// 先从overrideProperties里找
// 再从configList里按照顺序匹配,最先匹配上的为准
public Object getProperty(String key) {
if (overrideProperties.containsKey(key)) {
return overrideProperties.getProperty(key);
}
for (Configuration config : configList) { ... }
...
return firstMatchingConfiguration.getProperty(key);
return null; // 没找到就返回null
}
// 逻辑类似
@Override
public boolean containsKey(String key) { ... }
...
}它就是一个组合模式,只不过扩展了些更多功能。如果了解Spring的PropertySource属性源的小伙伴,阅读这块应该不太费力。
略。
关于Netflix Archaius对Commons Configuration核心API Configuration的扩展实现就介绍到这。
Netflix Archaius是基于Commons Configuration构建的,并且在其基础上进行扩展,提供了线程安全、性能更高的Configuration供以使用,其中ConcurrentCompositeConfiguration更是它组合配置实现原理的核心要义。
说明:
Commons Configuration也内置有CompositeConfiguration实现,只是功能上偏弱(比如不能控制顺序、中间插入等等)