[享学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
实现,只是功能上偏弱(比如不能控制顺序、中间插入等等)