【你好Archaius】四:Commons-Configuration Configuration事件监听机制以及热更新

每日一句

人一生下就会哭,笑是后来才学会的。所以忧伤是一种低级的本能,而快乐是一种更高级的能力

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。
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值