[享学Netflix] 十、Archaius对Commons Configuration核心API Configuration的扩展实现
- 2020 年 3 月 18 日
- 筆記
要相信:也有很多人比你更勤奋 代码下载地址: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的基础上做了扩展和增强。
对Configuration的扩展
在API层面,它最重要的便是在org.apache.commons.configuration.Configuration上提供了扩展。主要分为两个体系:ConcurrentMapConfiguration和AggregatedConfiguration。
ConcurrentMapConfiguration
该类使用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:你身边的配置管理专家 开始了解。
它的继承图如下:

DynamicConfiguration
动态配置,它会依赖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。
DynamicURLConfiguration
它继承自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秒轮询一次
ClasspathPropertiesConfiguration
标准的模块化配置方法。
假设您的应用程序使用了许多模块**(.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环境。
DynamicWatchedConfiguration
一个特殊的配置,它的特点是:更改底层数据源,然后就可以手动去同步器属性(也就是说可以换底层数据源…)。
// 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=18
AggregatedConfiguration
Aggregated:聚合的,合计的。
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实现组合模式的核心要义。
ConcurrentCompositeConfiguration
此类在列表结构中维护配置的层次结构,要确定属性值时,列表的顺序代表配置的降序优先级。这个规则和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实现,只是功能上偏弱(比如不能控制顺序、中间插入等等)


