每日一句
人一生下就会哭,笑是后来才学会的。所以忧伤是一种低级的本能,而快乐是一种更高级的能力
目录
CommonsConfiguration事件监听机制
前面介绍了Configuration的基本使用以及基本方法的实现。本文来了解一下CommonsConfiguration的事件监听机制。使CommonsConfiguration在增删改属性的时候能够发送对应的事件,让感兴趣的监听者实现对应的逻辑。
前面我们知道AbstractConfiguration继承了EventSource 所以AbstractConfiguration派生出来的配置类基本具有注册事件监听器的能力。而几乎所有的配置类都是AbstractConfiguration派生出来的 所以可以理解CommonsConfiguration中的所有配置类都具备注册监听器的能力
CommonsConfiguration事件源
ConfigurationEvent
ConfigurationEvent继承JDK中的EventObject
提供如下属性:
/** 事件类型 事件类型是一个个的常量 分布在各个配置类中*/
private int type;
/** 导致此次事件的属性名称 */
private String propertyName;
/** 导致此次事件的属性值*/
private Object propertyValue;
/** 是否是在更新之前 通常更新一个属性之前会发送一次事件 更新之后会发送一次
* 我们在上一节的源码展示中已经看到
*/
private boolean beforeUpdate;
ConfigurationErrorEvent
错误事件类型 继承 ConfigurationEvent
为了更好地控制客户端访问配置对象期间发生的错误,引入了专门针对异常的新事件侦听器机制:客户端可以将自己在配置对象中注册为错误侦听器,然后通知所有与源配置对象有关的内部错误
public class ConfigurationErrorEvent extends ConfigurationEvent
{
private static final long serialVersionUID = -7433184493062648409L;
//导致发生事件的原因
private Throwable cause;
}
ConfigurationListener&ConfigurationErrorListener
如果你需要监听属性的变化 请实现ConfigurationListener 并注册到AbstractConfiguration中。如果你需要监听属性变化过程中发生的异常需要实现ConfigurationErrorListener 接口并注册到AbstractConfiguration中。
这两个监听器也是很简单 只有一个方法需要实现。
public interface ConfigurationListener {
void configurationChanged(ConfigurationEvent event);
}
public interface ConfigurationErrorListener {
void configurationError(ConfigurationErrorEvent event);
}
EventSource
EventSource提供对监听器的维护,并且在时间发生时通知监听器的能力。
AbstractConfiguration就继承自EventSource,因此它的派生子类们均可以被监听。同时也是EventSource的唯一子类。
public class EventSource
{
/** 存储所有注册上来的监听器. */
private Collection<ConfigurationListener> listeners;
/** 存储注册上来的错误监听器.*/
private Collection<ConfigurationErrorListener> errorListeners;
/** 详细事件锁 */
private final Object lockDetailEventsCount = new Object();
/** 详细事件计数器. */
private int detailEvents;
//创建事件对象 并通知所有的监听器
protected void fireEvent(int type, String propName, Object propValue, boolean before){
if (checkDetailEvents(-1)){
Iterator<ConfigurationListener> it = listeners.iterator();
if (it.hasNext()){
ConfigurationEvent event =
createEvent(type, propName, propValue, before);
while (it.hasNext()){
it.next().configurationChanged(event);
}
}
}
}
}
//创建错误事件 并通知所有错误事件监听器
protected void fireError(int type, String propName, Object propValue, Throwable ex)
{
Iterator<ConfigurationErrorListener> it = errorListeners.iterator();
if (it.hasNext())
{
ConfigurationErrorEvent event =
createErrorEvent(type, propName, propValue, ex);
while (it.hasNext())
{
it.next().configurationError(event);
}
}
}
上面的两个方法没什么好说的。我们下面来说一下detailEvents 这个字段 字面意思是详细事件。什么意思?我们通过一个方法来加以说明 贴一段AbstractConfiguration的setProperty方法
public void setProperty(String key, Object value){
fireEvent(EVENT_SET_PROPERTY, key, value, true);
setDetailEvents(false);
try{
clearProperty(key);
addProperty(key, value);
}
finally{
setDetailEvents(true);
}
fireEvent(EVENT_SET_PROPERTY, key, value, false);
}
可以看到 setProperty方法包含了 两个方法 clearProperty和addProperty 而这两个方法被调用也会发送自己的事件。说到这 大家应该明白了 其实detailEvents 说白了就是来控制 方法内部的操作是否需要发送事件。
demo
下面运行一个demo
AbstractConfiguration configuration = new PropertiesConfiguration("app.properties");
configuration.addConfigurationListener(event -> {
System.out.println("是否更新之前:"+ event.isBeforeUpdate());
System.out.println("发生变化的属性名:" + event.getPropertyName());
System.out.println("发生变化的属性值:" + event.getPropertyValue());
System.out.println("事件类型:" + event.getType());
System.out.println("事件源:" + event.getSource());
});
configuration.setProperty("change.name" , "111");
打印出的结果:
是否更新之前:true
发生变化的属性名:change.name
发生变化的属性值:111
事件类型:3
事件源:org.apache.commons.configuration.PropertiesConfiguration@3551a94
是否更新之前:false
发生变化的属性名:change.name
发生变化的属性值:111
事件类型:3
事件源:org.apache.commons.configuration.PropertiesConfiguration@3551a94
上面的demo中会发现 setProperty内部的clearProperty和addProperty没有发出事件。
CommonsConfiguration热加载
上面我们聊了一下CommonsConfiguration的时间监听机制 其实很简单 主要就是EventSource和AbstractConfiguration这两个类来实现的。我们能检测到内存中属性变化了 但是文件的变化我们怎么来检测,下面我们就聊一下CommonsConfiguration的热加载机制(也叫热更新)。
ReloadingStrategy
决定是否重新加载配置的策略接口。
public interface ReloadingStrategy{
/**
* 设置当前策略管理的配置 注意是FileConfiguration
*/
void setConfiguration(FileConfiguration configuration);
/**
* 初始化策略
*/
void init();
/**
* 判断策略是否需要重新加载文件
*
*/
boolean reloadingRequired();
/**
* 通知策略文件已重新加载
*/
void reloadingPerformed();
}
继承关系
- VFSFileChangedReloadingStrategy:用于Commons VFS加载策略 忽略它
- InvariantReloadingStrategy:永远不会触发重新加载的策略。
- FileChangedReloadingStrategy:更改策略将在每次更改其基础文件时重新加载配置
- ManagedReloadingStrategy:一种基于管理请求重新加载配置的策略。专为JMX管理而设计
所以通过上面的介绍 我们只需要关心一个实现类FileChangedReloadingStrategy。
FileChangedReloadingStrategy
此重新加载策略不会主动监视配置文件,而是在访问属性时由其关联的配置触发。然后检查配置文件的最后修改日期,如果更改了,则重新加载。
为了避免在连续属性查找时永久访问磁盘,可以指定刷新延迟。这将导致在此延迟期间只检查一次配置文件的最后修改日期。此刷新延迟的默认值为5秒。此策略仅适用于FileConfiguration配置实例。
回过头再看AbstractFileConfiguration
public boolean reload(boolean checkReload){
synchronized (reloadLock){
//noReload是一个计数器 只有当没有正在加载的线程的时候才执行重新加载逻辑
if (noReload == 0){
try{
//对noReload赋值
enterNoReload();
//这里是关键 判断加载策略是否需要加载
if (strategy.reloadingRequired()){
if (getLogger().isInfoEnabled()){
getLogger().info("Reloading configuration. URL is " + getURL());
}
//执行刷新
refresh();
//通知策略
strategy.reloadingPerformed();
}
}
catch (Exception e){
fireError(EVENT_RELOAD, null, null, e);
if (checkReload){
return false;
}
}
finally{
//最终把noReload--
exitNoReload();
}
}
}
return true;
}
上面代码strategy.reloadingRequired()控制着是否需要refresh的逻辑。跟进reloadingRequired()
public boolean reloadingRequired() {
//如果没有正在重新加载 就进入
if (!reloading){
long now = System.currentTimeMillis();
//如果最后一次检查时间 超过了 refreshDelay才继续进入 默认间隔是5000ms
if (now > lastChecked + refreshDelay){
//更新最后一次检查时间
lastChecked = now;
//如果发生变化 返回true
//这个判断是否改变 是判断文件的修改时间是否大于记录的最后一次修改时间
if (hasChanged()){
if (logger.isDebugEnabled()){
logger.debug("File change detected: " + getName());
}
reloading = true;
}
}
}
return reloading;
}
只有调用AbstractFileConfiguration的reload方法之后才有可能触发重新加载策略。所以我们可以全局搜索一下在哪里调用了reload方法。
我们发现其实都是在get的时候触发reload。所以我们证明之前的结论:
修改了文件的值 需要我们get之后才能重新加载
一个demo
@Test
public void test1() throws ConfigurationException {
PropertiesConfiguration configuration = new PropertiesConfiguration("app.properties");
configuration.addConfigurationListener(event -> {
// 只监听到重新加载事件
if (event.getType() == PropertiesConfiguration.EVENT_RELOAD) {
System.out.println("配置文件重载...");
configuration.getKeys().forEachRemaining(k -> {
System.out.println("/t " + k + "-->" + configuration.getString(k));
});
}
});
FileChangedReloadingStrategy strategy = new FileChangedReloadingStrategy();
configuration.setReloadingStrategy(strategy);
new Thread(()->{
while (true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
configuration.getProperty("app.test");
}
}).start();
while (true){
}
}
我在这个过程中将配置文件中的app.test2:2222改成app.test2:333
输出
配置文件重载...
app.test-->111111
app.test1-->2222
app.test2-->2222
配置文件重载...
app.test-->111111
app.test1-->2222
app.test2-->333
这里有一个小的细节需要注意一下:在我们改完配置文件之后 需要把配置文件重新编译一下。idea可以右键文件 Recompile
总结
CommonsConfiguration的事件机制和热加载还是比较简单的。动手操作一波就可以了。到目前为止我们都是在讲的CommonsConfiguration 。从下一篇我们就开始Archaius。