[享学Netflix] 十、Netflix Archaius对Commons Configuration核心API Configuration的扩展实现

要相信:也有很多人比你更勤奋

–> 返回专栏总目录 <–
代码下载地址: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.ConfigurationApache Commons configuration的核心接口,Netflix Archaius就是依赖了它来实现内部的配置源。

而我们知道Netflix Archaius它是一个高效的,线程安全的配置管理库,因此它必然在Configuration的基础上做了扩展和增强。


对Configuration的扩展

在API层面,它最重要的便是在org.apache.commons.configuration.Configuration上提供了扩展。主要分为两个体系:ConcurrentMapConfigurationAggregatedConfiguration


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) { ... }

}

这个子类确定以及肯定了使用URLConfigurationSourceFixedDelayPollingScheduler来完成动态属性轮询,因此使用子类的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实现,只是功能上偏弱(比如不能控制顺序、中间插入等等)

分隔线

声明

原创不易,码字不易,多谢你的点赞、收藏、关注。把本文分享到你的朋友圈是被允许的,但拒绝抄袭。你也可【左边扫码/或加wx:fsx641385712】邀请你加入我的 Java高工、架构师 系列群大家庭学习和交流。
往期精选

发布了309 篇原创文章 · 获赞 467 · 访问量 40万+

猜你喜欢

转载自blog.csdn.net/f641385712/article/details/104450517