每日一句
这个世界上没有优秀的概念,只有脚踏实地的结果
上一篇通过小例子对archaius有了一个简单的认识。对ConfigurationManager获取一个配置的流程分析了一篇。但是过程中遇到了几个我们还没见过的类。例如:ConcurrentCompositeConfiguration、DynamicURLConfiguration。我们能感觉他也是Configuration的子类,Configuration是commons-configuration里面的接口,那他们一定就是archaius对Configuration接口的扩展!
继承关系
再一次打开这个继承关系 我们发现configuration的实现类多出了很多,它们的包名为com.netflix.config 这些类就是archaius对Configuration接口的扩展了。
- ConcurrentMapConfiguration:此类使用ConcurrentHashMap读取/写入属性以实现高吞吐量和线程安全性
- AggregatedConfiguration:聚合配置类
- ConcurrentCompositeConfiguration:聚合配置的实现,维护了配置列表,列表的顺序代表配置的优先级。(重要)
- DynamicConfiguration:动态配置 它会依赖调度器
- DynamicURLConfiguration:继承DynamicConfiguration 配置源URLConfigurationSource (重要)
- ClasspathPropertiesConfiguration:基于常规标准的模块化配置方法 假设您的应用程序利用了许多模块(.jar文件)并且需要属性支持,此类提供了一种基于约定的方法来从每个jar中扫描和加载属性在来自特定位置的类路径中
- DynamicWatchedConfiguration: 配置改变 配置跟着改变
下面我们就分别介绍这些扩展的配置类。
ConcurrentMapConfiguration
这个可以说是archaius扩展commons-configuration的基类。所有的扩展类都是在改类的基础上再一次扩展的。也就是说archaius扩展的配置类都是线程安全的。
属性说明:
protected Map<String,Object> map;
//listeners和errorListeners看起来有点熟悉 没错EventSource里面也有定义
private Collection<ConfigurationListener> listeners = new CopyOnWriteArrayList<ConfigurationListener>();
private Collection<ConfigurationErrorListener> errorListeners = new CopyOnWriteArrayList<ConfigurationErrorListener>();
private static final Logger logger = LoggerFactory.getLogger(ConcurrentMapConfiguration.class);
//这里使用分段锁的机制来提升性能
private static final int NUM_LOCKS = 32;
private ReentrantLock[] locks = new ReentrantLock[NUM_LOCKS];
public static final String DISABLE_DELIMITER_PARSING = "archaius.configuration.disableDelimiterParsing";
public ConcurrentMapConfiguration() {
//存储配置的是ConcurrentHashMap
map = new ConcurrentHashMap<String,Object>();
for (int i = 0; i < NUM_LOCKS; i++) {
locks[i] = new ReentrantLock();
}
//是否禁用使用分割符解析属性 如果禁用a,b,c将不再被解析成list
String disableDelimiterParsing = System.getProperty(DISABLE_DELIMITER_PARSING, "false"); super.setDelimiterParsingDisabled(Boolean.valueOf(disableDelimiterParsing));
}
- 存储属性的数据结构是ConcurrentHashMap
- 采用分段加锁方式
- 重写了EventSource监听器和事件相关的方法 完全弃用了EventSource
方法说明
//设置属性
public void setProperty(String key, Object value) throws ValidationException
{
if (value == null) {
throw new NullPointerException("Value for property " + key + " is null");
}
//设置前发送事件 代码我们再熟悉不过了
fireEvent(EVENT_SET_PROPERTY, key, value, true);
//真正的设置逻辑
setPropertyImpl(key, value);
//设置后发送事件
fireEvent(EVENT_SET_PROPERTY, key, value, false);
}
//这个方法再它的父类 EventSource里面也有被重写 其实相差不大 多了对异常的处理
protected void fireEvent(int type, String propName, Object propValue, boolean beforeUpdate) {
if (listeners == null || listeners.size() == 0) {
return;
}
ConfigurationEvent event = createEvent(type, propName, propValue, beforeUpdate);
for (ConfigurationListener l: listeners)
{
try {
l.configurationChanged(event);
} catch (ValidationException e) {
if (beforeUpdate) {
throw e;
} else {
logger.error("Unexpected exception", e);
}
} catch (Throwable e) {
logger.error("Error firing configuration event", e);
}
}
}
//设置属性 因为底层存储的是map 所以设置的也非常简单 直接put
//但是如果设置的值有分隔符 会当做list处理
protected void setPropertyImpl(String key, Object value) {
if (isDelimiterParsingDisabled()) {
map.put(key, value);
} else if ((value instanceof String) && ((String) value).indexOf(getListDelimiter()) < 0) {
map.put(key, value);
} else {
Iterator it = PropertyConverter.toIterator(value, getListDelimiter());
List<Object> list = new CopyOnWriteArrayList<Object>();
while (it.hasNext())
{
list.add(it.next());
}
if (list.size() == 1) {
map.put(key, list.get(0));
} else {
map.put(key, list);
}
}
}
其他的方法不再一一列举了 都是很简单的方法。
DynamicConfiguration
DynamicConfiguration继承ConcurrentMapConfiguration 所以监听器和发送事件的能力 它天然就具备。 如果配置源中的配置更改,则此配置中的属性值将在运行过程中动态更改。所以DynamicConfiguration的子类天然具有动态性。
DynamicConfiguration源码比较简单 这里就不贴出来了 为了节省篇幅。
DynamicConfiguration demo
PolledConfigurationSource polledConfigurationSource = new URLConfigurationSource();
AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler();
DynamicConfiguration dynamicConfiguration = new DynamicConfiguration(polledConfigurationSource , scheduler);
while (true){
ConfigurationUtils.dump(dynamicConfiguration , System.out);
System.out.println();
Thread.sleep(1000);
}
上面的例子就会加载classpath下面的config.properties文件 并且延迟30s加载一次 后面每60s加载一次。所以上面代码就能实现属性动态性。
我们不难发现其实 实现动态属性就是实例化一个PolledConfigurationSource 再实例化一个调度器AbstractPollingScheduler 几乎都是固定的操作。所以我们的DynamicURLConfiguration就应运而生了。
DynamicURLConfiguration
从名字看 就是专门为URLConfigurationSource而生的一个配置扩展。这个类的源代码其实就是吧我们上面一个demo的代码 改进一下。我们也不贴出来了。直接看demo
DynamicURLConfiguration dynamicURLConfiguration = new DynamicURLConfiguration();
while (true){
ConfigurationUtils.dump(dynamicURLConfiguration , System.out);
System.out.println();
Thread.sleep(1000);
}
上面代码能实现和上一个demo一样的功能。但是简洁多了。
DynamicWatchedConfiguration
利用观察者模式 在数据源更改的时候发送监听事件来让DynamicWatchedConfiguration做出相应的更改。但是并不常用!
public class DynamicWatchedConfiguration extends ConcurrentMapConfiguration implements WatchedUpdateListener {
//监听的配置源
private final WatchedConfigurationSource source;
//是否忽略删除 如果忽略删除数据源里面的配置 配种中心不会做出响应的删除操作
private final boolean ignoreDeletesFromSource;
//动态属性更新器
private final DynamicPropertyUpdater updater;
}
DynamicWatchedConfiguration:继承ConcurrentMapConfiguration并且实现了WatchedUpdateListener接口 所以存储配置的容器还是ConcurrentHashMap。当WatchedConfigurationSource中的配置做出改变的时候 需要通知WatchedUpdateListener做出相应的改变。
DynamicWatchedConfiguration demo
//自定义一个配置源 并且是一个事件源 也就是对监听器做了操作
public static class CustomWatchedConfigurationSource implements WatchedConfigurationSource {
List<WatchedUpdateListener> listeners = new CopyOnWriteArrayList<>();
//数据 从数据源加载的数据
private Map<String , Object> dataSource;
public CustomWatchedConfigurationSource(){}
//也可以通过构造方法初始化数据
public CustomWatchedConfigurationSource(Map<String , Object> dataSource){
initData(dataSource);
}
//可以在init方法中加载数据
public void initData(Map<String , Object> dataSource){
this.dataSource = dataSource;
}
@Override
public void addUpdateListener(WatchedUpdateListener l) {
listeners.add(l);
}
@Override
public void removeUpdateListener(WatchedUpdateListener l) {
listeners.remove(l);
}
@Override
public Map<String, Object> getCurrentData() throws Exception {
return this.dataSource;
}
public void fireEvent(){
WatchedUpdateResult full = WatchedUpdateResult.createFull(this.dataSource);
for (WatchedUpdateListener listener : listeners){
listener.updateConfiguration(full);
}
}
}
@Test
public void test1(){
Map<String ,Object> data = new HashMap<>();
data.put("person.name" , "coredy");
//创建一个事件源实例 并将数据源给到它(实际的过程可能要根据具体的业务来更改)
CustomWatchedConfigurationSource watchedConfigurationSource = new CustomWatchedConfigurationSource(data);
//实例化观察者事件配置 并将事件源传给他
DynamicWatchedConfiguration configuration = new DynamicWatchedConfiguration(
watchedConfigurationSource);
ConfigurationUtils.dump(configuration , System.out);
System.out.println("\n===================");
//改变数据源 DynamicWatchedConfiguration配置也会响应的改变
data.put("person.name" , "coredy1");
data.put("person.gae" , "11");
//这里根据具体的逻辑触发事件
watchedConfigurationSource.fireEvent();
ConfigurationUtils.dump(configuration , System.out);
}
ClasspathPropertiesConfiguration
假如你的项目有多个模块,该配置类会从特定类路径的每个jar中扫描和加载属性位置,这个位置默认是META-INF/conf/config.properties。
//默认的路径
static String propertiesResourceRelativePath = "META-INF/conf/config.properties";
//单例模式
static ClasspathPropertiesConfiguration instance = null;
//无法直接实例化
private ClasspathPropertiesConfiguration(){
}
public String getPropertiesResourceRelativePath() {
return propertiesResourceRelativePath;
}
//可以通过静态方法更改默认的文件路径
public static void setPropertiesResourceRelativePath(
String propertiesResourceRelativePath) {
ClasspathPropertiesConfiguration.propertiesResourceRelativePath = propertiesResourceRelativePath;
}
//这个方法对 我们没有任何意义 因为我们没法拿到instance实例
public Properties getProperties() {
return instance !=null ? instance.getProperties() : new Properties();
}
//加载配置
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);
}
}
//可以看到是通过ConfigurationManager来完成加载的。通过上一节我们知道 ConfigurationManager内部维护了一个组合配置实例 是单利模式
//所以下面代码是将jar包中的config.properties配置加载到了ConfigurationManager管理的配置实例中 所以 所以和当前的instance没有一毛钱的关系。
private static void loadResources(String resourceName) throws Exception
{
ConfigurationManager.loadPropertiesFromResources(resourceName);
log.debug("Added properties from:" + resourceName);
}
ClasspathPropertiesConfiguration demo
新建了一个模块,添加了config.properties文件。然后再主项目增加如下测试代码
ClasspathPropertiesConfiguration.initialize();
ConfigurationUtils.dump(ConfigurationManager.getConfigInstance() , System.out);
这样我们就可以吧jar内的config.properties加载到ConfigurationManager的实例中。
AggregatedConfiguration
组合配置,它在Configuration接口上又增加了如下的方法。
//在其子类会维护一个 名字和配置实例映射的map还维护一个配置的列表 如果名字为null直接添加到列表中
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会实现上诉的方法。
ConcurrentCompositeConfiguration
是AggregatedConfiguration唯一实现类 同时也继承了ConcurrentMapConfiguration。内部维护了一个配置列表。配置在列表中的顺序决定了配置的优先级。我们通过上一篇知道ConfigurationManager中维护的也正是ConcurrentCompositeConfiguration的实例。所以我们平时使用的Configuration就是组合配置。它是archaius实现多配置源配置、配置优先级定义的核心。
属性:
//名字和配置的映射 以便我们可以通过name方便找到对应的Configuration
private Map<String, AbstractConfiguration> namedConfigurations = new ConcurrentHashMap<String, AbstractConfiguration>();
//配置列表 getProperty就是从这个列表中遍历查找
private List<AbstractConfiguration> configList = new CopyOnWriteArrayList<AbstractConfiguration>();
public static final int EVENT_CONFIGURATION_SOURCE_CHANGED = 10001;
//是否传播事件到父类
private volatile boolean propagateEventToParent = true;
//覆盖配置
private AbstractConfiguration overrideProperties;
//内存配置 也就是我们直接调用该实例的setProperty会将配置加到这个实例里面 我们后面把它称作内存配置
//所以在上一篇我们断点发现里面莫名的多了一项配置
private AbstractConfiguration containerConfiguration;
//这是一个标志 用来标志新增的配置是放在内存配置的前面还是放在所有配置的末尾
private volatile boolean containerConfigurationChanged = true;
//事件传播 这里的事件传播其实就是 子配置 在接收到事件的时候把事件传递到组合配置
private ConfigurationListener eventPropagater = new ConfigurationListener() {
@Override
public void configurationChanged(ConfigurationEvent event) {
boolean beforeUpdate = event.isBeforeUpdate();
if (propagateEventToParent) {
int type = event.getType();
String name = event.getPropertyName();
Object value = event.getPropertyValue();
Object finalValue;
switch(type) {
case HierarchicalConfiguration.EVENT_ADD_NODES:
case EVENT_CLEAR:
case EVENT_CONFIGURATION_SOURCE_CHANGED:
fireEvent(type, name, value, beforeUpdate);
break;
case EVENT_ADD_PROPERTY:
case EVENT_SET_PROPERTY:
if (beforeUpdate) {
// we want the validators to run even if the source is not
// the winning configuration
fireEvent(type, name, value, beforeUpdate);
} else {
AbstractConfiguration sourceConfig = (AbstractConfiguration) event.getSource();
AbstractConfiguration winningConf = (AbstractConfiguration) getSource(name);
if (winningConf == null || getIndexOfConfiguration(sourceConfig) <= getIndexOfConfiguration(winningConf)) {
fireEvent(type, name, value, beforeUpdate);
}
}
break;
case EVENT_CLEAR_PROPERTY:
finalValue = ConcurrentCompositeConfiguration.this.getProperty(name);
if (finalValue == null) {
fireEvent(type, name, value, beforeUpdate);
} else {
fireEvent(EVENT_SET_PROPERTY, name, finalValue, beforeUpdate);
}
break;
default:
break;
}
}
}
};
添加一个Configuration
public void addConfiguration(AbstractConfiguration config, String name){
//containerConfigurationChanged这个属性就是刚刚我们上面说的 如果值为true 添加的子配置将放在列表最末尾
//值为false 添加到containerConfiguration前面
if (containerConfigurationChanged) {
addConfigurationAtIndex(config, name, configList.size());
} else {
addConfigurationAtIndex(config, name, configList.indexOf(containerConfiguration));
}
}
public void addConfigurationAtIndex(AbstractConfiguration config, String name, int index)
throws IndexOutOfBoundsException {
//如果本来就不包含当前添加的配置 直接添加到对应的位置
if (!configList.contains(config)) {
checkIndex(index);
configList.add(index, config);
//如果名字不为null 还将被添加到namedConfigurations
if (name != null) {
namedConfigurations.put(name, config);
}
//这里很关键 向子配置容器中添加一个监听器 这个监听器就是前面我们注释的该类的一个属性 所以每当子配置容器的配置项发生变化 就会触发这个监听器
//这个监听器就会调用ConcurrentCompositeConfiguration的fireEvent方法 发出事件
//这就是事件传播 子配置容器的事件传播到上层父配置容器(这里的父配种容器就是组合配置容器)
config.addConfigurationListener(eventPropagater);
//发送EVENT_CONFIGURATION_SOURCE_CHANGED事件
fireEvent(EVENT_CONFIGURATION_SOURCE_CHANGED, null, null, false);
} else {
logger.warn(config + " is not added as it already exits");
}
}
其他的方法都比较简单
ConcurrentCompositeConfiguration demo
AbstractConfiguration configInstance = ConfigurationManager.getConfigInstance();
ConcurrentCompositeConfiguration concurrentCompositeConfiguration = (ConcurrentCompositeConfiguration)configInstance;
Configuration configuration = concurrentCompositeConfiguration.getConfiguration("archaius.dynamicPropertyFactory.URL_CONFIG");
//调用setProperty方法 会触发fireEvent 所以会被eventPropagater监听 因为每一个子配置都会被设置进一个ConfigurationListener
//而eventPropagater又会调用ConcurrentCompositeConfiguration的fireEvent 而fireEvent被ConcurrentCompositeConfiguration重写了所以不会造成死循环
// 这样就是事件传播到ConcurrentCompositeConfiguration了
configuration.setProperty("name" , "1111");
ConfigurationWithPollingSource
该类包含一个动态的配置源和调度程序。所有对配置的操作都是委托给内部的配置源。通过调度器来达到动态配置。
public class ConfigurationWithPollingSource implements Configuration {
//配置源
private final Configuration config;
//调度程序
private final AbstractPollingScheduler scheduler;
//这个构造函数需要一个配置容器 一个配置源 一个调度程序
public ConfigurationWithPollingSource(Configuration config, PolledConfigurationSource source,
AbstractPollingScheduler scheduler) {
this.config = config;
this.scheduler = scheduler;
//实例化ConfigurationWithPollingSource时候开始执行调度任务 达到了动态配置的目的
scheduler.startPolling(source, this);
}
}
//其他的方法均有config来代理实现
容器用来存储 配置源中的配置 配置源用来被调度程序调度。
demo
@Test
public void test3() {
ConcurrentMapConfiguration propertiesConfiguration = new ConcurrentMapConfiguration();
URLConfigurationSource urlConfigurationSource = new URLConfigurationSource();
AbstractPollingScheduler scheduler = new FixedDelayPollingScheduler();
ConfigurationWithPollingSource configurationWithPollingSource =
new ConfigurationWithPollingSource(propertiesConfiguration , urlConfigurationSource , scheduler);
ConfigurationUtils.dump(configurationWithPollingSource , System.out);
}
Netflix Archaius对commons-configuration的扩展就到这。它是基于commons-configuration并且在它的基础上进一步的扩展,提供了线程安全,性能更高的Configuration。并提供了天然的支持动态的DynamicURLConfiguration和多数据源组合配置ConcurrentCompositeConfiguration。