[享学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版本