[享学Netflix] 三、Apache Commons Configuration2.x全新的事件-监听机制
- 2020 年 3 月 18 日
- 笔记
写好代码是个人素养,不是老板要求,更不是为了秀给同事看 代码下载地址: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
版本