[享学Netflix] 三、Apache Commons Configuration2.x全新的事件-监听机制

拟墨画扇 提交于 2020-02-18 19:16:38

写好代码是个人素养,不是老板要求,更不是为了秀给同事看

–> 返回专栏总目录 <–
代码下载地址:https://github.com/f641385712/netflix-learning

前言

前面文章重点介绍了Apache Commons Configuration1.x的使用以及原理,作为2013就已经停更的技术,本确实没有太大必要再去学它,但就因为Netflix一直还依赖它,所以这就变成了有必要。

然而作为当下的主流的2.x版本,自然也不能忽略。它几乎完全重写了1.x的代码,所以自然是不向下兼容的,并且因为包名都不一样,所以2.x和1.x是可以共存的

由于在实际使用中,那是100%推荐使用2.x版本,因此花点时间精力去了解它就变得更加具有现实意义了。本篇文章将以事件-监听机制为切入点,介绍Apache Commons Configuration2.x全新的事件-监听机制。


正文

2.x完全推翻了1.x对事件-监听机制,重新设计了一套全新API。可能它学习了Spring,使得它和Spring的事件机制颇有几分相似之处,所以理解起来对读者来说会更加亲切。


Event

继承自JDK标准事件java.util.EventObject。是所有事件的基类,同Spring的org.springframework.context.ApplicationEvent

public class Event extends EventObject {

	// root event。所以事件类型都直接/间接的继承自它
	// 如果你监听的是这种事件类型:那就是监听所有的事件
	public static final EventType<Event> ANY = new EventType<>(null, "ANY");

	// 唯一构造器  source和EventType是必须的,确定了你发送的事件源 和类型
	// 比如对Person的新增。person是事件源,新增是事件类型
	public Event(final Object source, final EventType<? extends Event> evType) { ... }
	
}

内置如下几个事件:

在这里插入图片描述


ConfigurationEvent

不解释。

public class ConfigurationEvent extends Event {

	// 该事件源内置的事件类型们,是public的哦
	public static final EventType<ConfigurationEvent> ANY = new EventType<>(Event.ANY, "CONFIGURATION_UPDATE");
	public static final EventType<ConfigurationEvent> ADD_PROPERTY = new EventType<>(ANY, "ADD_PROPERTY");
	... // 增删改等非常多的操作
    private final String propertyName;
    private final Object propertyValue;
    private final boolean beforeUpdate;
    ...
}

ConfigurationErrorEvent

它不再像1.x一样继承自ConfigurationEvent,而是直接继承自Event

public class ConfigurationEvent extends Event {

    public static final EventType<ConfigurationEvent> ANY = new EventType<>(Event.ANY, "CONFIGURATION_UPDATE");
    public static final EventType<ConfigurationEvent> ADD_PROPERTY = new EventType<>(ANY, "ADD_PROPERTY");
    ... // 省略其它事件类型
    
    private final String propertyName;
    private final Object propertyValue;
    private final boolean beforeUpdate;
	...
}

ReloadingEvent

Reloading:重新加载,它在Commons Configuration了是个很重要的概念,一般服务于热更新、热加载等重要功能。
这个事件比较特殊,可重点关注下。

public class ReloadingEvent extends Event {

	// 注意:它仅有这一个事件类型,木有子类型
    public static final EventType<ReloadingEvent> ANY = new EventType<>(Event.ANY, "RELOAD");
    
    // reloading时发送的额外数据,没有可为null
    private final Object data;

	// 小细节:从这个方法可以得出结论
	// 发送这个事件的事件源:一定是ReloadingController
    public ReloadingController getController() {
        return (ReloadingController) getSource();
    }
}

发送这个事件的事件源:一定是ReloadingController


ConfigurationBuilderEvent

它也是一个很特殊的事件,该事件会在ConfigurationBuilder里发出。

public class ConfigurationBuilderEvent extends Event {

    public static final EventType<ConfigurationBuilderEvent> ANY = new EventType<>(Event.ANY, "BUILDER");
	
	// 在BasicConfigurationBuilder#resetResult()方法被调用的时候,此事件发出
    public static final EventType<ConfigurationBuilderEvent> RESET = new EventType<>(ANY, "RESET");
	// ConfigurationBuilder#getConfiguration()被调用时候,获取时。发送这个事件
	// 当时请注意:这这是发起了request,但不一定get成功了(中途可能抛出异常嘛)
    public static final EventType<ConfigurationBuilderEvent> CONFIGURATION_REQUEST = new EventType<>(ANY, "CONFIGURATION_REQUEST");


	// 同样的,这个事件源必须是`ConfigurationBuilder`
    @Override
    public ConfigurationBuilder<?> getSource() {
        return (ConfigurationBuilder<?>) super.getSource();
    }
}

它的事件源一定是一个ConfigurationBuilder实例。


ConfigurationBuilderResultCreatedEvent

它对父类ConfigurationBuilderEvent扩展,增加事件类型RESULT_CREATED,表示Result创建成功后的事件(注意和父类的CONFIGURATION_REQUEST的区别哦)。

并且该事件源还提供了对Configuration的访问方法,因为发送该时间肯定能确定Configuration实例已经创建成功了嘛~~~

public class ConfigurationBuilderResultCreatedEvent extends ConfigurationBuilderEvent {

	// 它表示Result获取**成功**后,发送的事件
    public static final EventType<ConfigurationBuilderResultCreatedEvent> RESULT_CREATED = new EventType<>(ANY, "RESULT_CREATED");

	// 提供对配置对象的访问,毕竟发送此事件代表肯定创建成功了嘛~
	// 绝大部分情况是Configuration
	private final ImmutableConfiguration configuration;
}

EventType

事件类型。每个类型可以有它属于的父类型,以及名称。Spring里并没有提出时间类型的概念,而是通过Class类型去区分,这一点上我倒觉得Commons Configuration更有优势些~

public class EventType<T extends Event> implements Serializable {

	private final EventType<? super T> superType;
	private final String name;
	
	... // 省略构造器

	// 获取到该类型所有的父类型(递归调用到顶层)
	public static Set<EventType<?>> fetchSuperEventTypes(final EventType<?> eventType) { ... }
	// derivedType是否是baseType的子类型(递归去比较)
	public static boolean isInstanceOf(final EventType<?> derivedType, final EventType<?> baseType) { ... }
}

绝大多数情况下,你并不需要去自定义自己的EventType,使用现成的即可。


EventListener

监听器,监听指定的事件(类型),它是个函数式接口。可类比org.springframework.context.ApplicationListener,它也是个函数式接口,接口方法叫:onApplicationEvent(E event)

public interface EventListener<T extends Event> {
	void onEvent(T event);
}

列出几个内置常用实现:


AutoSaveListener

实现基于文件自动保存机制的侦听器类配置,同时它也实现了FileHandlerListener从而可以让文件自动保存。它的访问权限是default,外部并不能直接使用它。


ReloadingBuilderSupportListener

一个内部使用的帮助类,用于向任意Configuration对象添加Reloading支持:这种支持包括自动resetResult()resetReloadingState()重置状态,所以是非常有必要的。

// 说明:它监听的是所有事件哦~~~~
final class ReloadingBuilderSupportListener implements EventListener<Event> {
	private final BasicConfigurationBuilder<?> builder;
	private final ReloadingController reloadingController;
	... // 省略构造器赋值

	// 当监听的事件是:RESULT_CREATED,也就是Configuration被成功创建return出去后,给重置其ReloadingState
	// 也就是说保证获取出去的对象仍可以热加载..

	// 而如果是其它事件,resetResult() -> result =null并且发出RESET事件
    @Override
    public void onEvent(final Event event) {
        if (ConfigurationBuilderResultCreatedEvent.RESULT_CREATED.equals(event.getEventType())) {
            reloadingController.resetReloadingState();
        } else {
            builder.resetResult();
        }
    }

	// =======同时它还提供一个静态方法,方便你绑定======
    public static ReloadingBuilderSupportListener connect(
            final BasicConfigurationBuilder<?> configBuilder,
            final ReloadingController controller) {
            
		final ReloadingBuilderSupportListener listener = new ReloadingBuilderSupportListener(configBuilder, controller);
		// 把该时间绑定在ReloadingController上
		// 说明:ReloadingController它自己是个EventSource哦,所以可以监听它
		// 并且它可以发出ReloadingEvent事件出来。所以发现,当发出ReloadingEvent出来时,触发本监听器的resetResult操作
		controller.addEventListener(ReloadingEvent.ANY, listener);

		// 给builder监听上RESULT_CREATED这个事件,当RESULT_CREATED到达此监听器时候会清空Result
		configBuilder.installEventListener( ConfigurationBuilderResultCreatedEvent.RESULT_CREATED, listener);

		return listener;
	}
}

该监听器对外暴露的方法其实只有:ReloadingBuilderSupportListener.connect(this, controller);,在BasicConfigurationBuilder创建ReloadingController的时候会用到。

EventListenerRegistrationData

Registration:注册,登记。包含事件侦听器注册信息的数据类

// 为注册监听器时提供数据
public final class EventListenerRegistrationData<T extends Event> {
	private final EventType<T> eventType;
	private final EventListener<? super T> listener;
}

EventListenerList

这个就比较简单了,内部管理维护着一堆监听器,并且提供注册、移除、清空、获取等方法。

public class EventListenerList {
	private final List<EventListenerRegistrationData<?>> listeners;
	// 可以看到对它的增删改都是线程安全的
    public EventListenerList() {
        listeners = new CopyOnWriteArrayList<>();
    }
	... //addEventListener/removeEventListener/fire/getEventListeners


	// 拿到监听此事件类型(以及其所有子事件类型)所有的监听器们
	public <T extends Event> List<EventListenerRegistrationData<? extends T>> getRegistrationsForSuperType(EventType<T> eventType) { ... }
	...
}

使用示例

介绍了这么多,从API层面应该能深刻感受到它和1.x的设计完全是两码事里,反倒和Spring的设计几乎如出一辙。

那么在使用层面是否友好呢?请看下面这个简单案例:

@Test
public void fun100() throws ConfigurationException {
    Configurations configs = new Configurations();
    PropertiesConfiguration config = configs.properties("1.properties");

    // 监听ADD_PROPERTY添加属性事件
    config.addEventListener(ConfigurationEvent.ADD_PROPERTY, event -> {
        if (!event.isBeforeUpdate()) {
            System.out.printf("成功添加属性:%s = %s", event.getPropertyName(), event.getPropertyValue());
        }
    });

    config.addProperty("name","YourBatman");
}

控制台打印:

成功添加属性:name = YourBatman

可以看到,核心API虽然变化极大,但在最基础的使用层面其实变化并不太大,对使用者相对友好。但话说回来,还是有不小切换、以及理解成本的。


使用场景

监听器的典型使用场景:记录配置文件的修改记录


总结

关于Apache Commons Configuration2.x版本的事件-监听机制就介绍到这了,以它为例可以看到2.x相较于1.x的改动是非常之大的,这就是为何Apache团队不在1.x上直接升级而选择重新命名的最重要的原因了吧。

以点见面,2.x各个部分改动均不小,所以从1.x的知识迁移到2.x并不会很平滑,甚至需要重新学习,本系列也会逐渐把它展示在大家面前,以便工作中自由的使用Apache Commons Configuration2.x版本。

分隔线

声明

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!