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