写好代码是个人素养,不是老板要求,更不是为了秀给同事看
–> 返回专栏总目录 <–
代码下载地址: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高工、架构师 系列群大家庭学习和交流。
来源:CSDN
作者:YourBatman
链接:https://blog.csdn.net/f641385712/article/details/104374901