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