本文分為三個部分:
- Observer(觀察者)
- Guava EventBus詳解
- Guava EventBus使用示例
1. Observer(觀察者)
1.1 背景
我們設計系統時,常常會將系統分割為一系列相互協作的類,使得這些類之間可以各自獨立地復用,系統整體結構也會比較清晰。這是一種最基本的面向對象的設計方式,大多數情況下也非常有效。但是,如果這些相互協作的類之間的“協作”關系比較復雜,那么就會有副作用:需要維護這些類對象間的狀態一致性。
我們以一個數據可視化系統為例來說明:
(1)數據可視化系統可以分割為兩個主要的類:定義應用數據的類和負責界面表示的類;換言之,數據可視化系統中存在兩類對象:應用數據對象(用於封裝底層數據源中的數據)和界面對象(如:表格對象、柱形圖對象、餅圖對象等等);
(2)數據可視化系統中類(對象)之前的協作關系:如果應用數據對象發生變化(表示底層數據源中的數據發生變化),則表格對象、柱形圖對象、餅圖對象需要被立即更新;反過來也是如此,如果表格對象發生變化(假設表格對象的變化會導致底層數據源中的數據發生變化),則數據對象需要被立即更新,從而導致柱形圖對象、餅圖對象也需要被更新;
數據對象、表格對象、柱形圖對象、餅圖對象之間的協作關系如下:

表格對象、柱形圖對象、餅圖對象分別使用不同的表示形式描述同一個數據對象的信息,它們之間相互並不知道對方的存在,這樣我們可以根據需要單獨復用這些對象(類)。但在我們數據可視化系統的場景中,它們表現的“互相知道”(參見上述(2))。
這種“互相知道”的行為意味着表格對象、柱形圖對象、餅圖對象都需要依賴於數據對象,數據對象的任何狀態變化都應立即通過它們,某一界面對象的變化實際也是通過數據對象反映給其它界面對象的。同時也沒有理由將依賴於數據對象的界面對象數目限定為三個,對相同的數據可以有任意數目的不同用戶界面。
綜上所述,數據、表格、柱形圖、餅圖這幾個互相協作的類的副作用表現為:需要維護數據對象、表格對象、柱形圖對象(可能還有其它界面對象)之間狀態的一致性,某一對象的狀態發生變化,需要立即更新其它對象的狀態。
1.2 定義
觀察者模式用於定義對象間的一種一對多的依賴關系,當一個對象的狀態發生變化時,所有依賴於它的對象都將得到通知並被“自動”更新。它有兩個關鍵對象:目標(Subject)和觀察者(Observer)。一個目標可以有任意數量的依賴它的觀察者。一旦目標的狀態發生改變,所有的觀察者都得到通知。作為對這個通知的響應,每個觀察者都將
查詢目標以使其狀態與目標狀態同步。
這種交互也稱為“發布—訂閱”(publish-subscribe)。目標是通知的發布者。它發出通知時並不需要知道誰是它的觀察者。可以有任意數目的觀察者訂閱並接收通知。
注:參考1.1中的數據可視化系統示例,“目標”為數據對象,“觀察者”為表格對象、柱形圖對象、餅圖對象。
1.3 結構與協作

Subject(目標)
—目標知道它的觀察者,可以有任意多個觀察者觀察同一個目標。
—提供注冊和刪除觀察者對象的接口(attach、detach)。
Observer(觀察者)
—為那些在目標發生改變時需要獲得通知的對象定義一個更新接口(update)。
ConcreteSubject(具體目標)
—將有關狀態存入各個ConcreteSubject對象。
—當它的狀態發生變化時,向它的各個觀察者發出通知(notify)。
ConcreteObserver(具體觀察者)
—維護一個指向ConcreteSubject對象的引用。
—存儲有關狀態,這些狀態應與目標的狀態保持一致。
—實現Observer的更新接口以使自身狀態與目標的狀態保持一致。
下面的交互圖說明了一個目標和兩個觀察者之間的協作:

(1)當ConcreteSubject發生任何可能導致其觀察者與其本身狀態不一致的改變時(aConcreteSubject的改變請求由aConcreteObserver通過setState()發出),它將通知它的各個觀察者(notify());
(2)ConcreteObserver對象在得到一個具體的改變通知后,可向目標對象查詢信息(getState()),並使用這些信息使它的狀態與目標對象的狀態一致。
注意:
(1)發出改變請求的Observer對象並不立即更新,而是將其推遲到它從目標得到一個通知之后;
(2)notify()不總是由目標對象調用,它也可被一個觀察者或其它對象調用;
1.4 更改管理器(ChangeManager)
當目標和觀察者間的依賴關系特別復雜時,就需要一個維護這些關系的對象,我們稱之為更改管理器(ChangeManager)。

ChangeManager有三個責任:
(1)它將一個目標映射到它的觀察者並提供相應的接口(register、unregister)來維護這個映射,這就不需要由目標來維護對其觀察者的引用,反之亦然;
(2)它定義一個特定的更新策略(這里的更新策略是更新所有依賴於這個目錄的觀察者);
(3)根據一個目標的請求(notify),它更新所有依賴於這個目標的觀察者;
2. Guava EventBus詳解
通常情況下,一個系統(這里特指進程)內部各個組件之間需要完成事件分發或消息通信,往往需要這些組件之間顯式地相互引用。如果這些組件數目較多,且相互引用關系復雜就會出現副作用:需要維護這些相互引用的組件之間的狀態一致性。
觀察者模式(Observer)用於解決上述問題,EventBus就是該模式的一個實現,它是Google Guava提供的一個組件。
EventBus使用“發布—訂閱”的通信模式,使得系統(進程)內各個組件之間無需相互顯式引用即可完成事件分發或消息通信,如下圖所求:

它的設計結構非常符合觀察者模式,
目標:事件(Event);
觀察者:事件監聽器(EventListener)(EventHandler是EventListener的封閉);
每一次事件的發生或變化,EventBus負責將其派發(post)至相應的事件監聽器,同一事件的事件監聽器可以有多個。
2.1 EventBus register
2.1.1 作用
維護事件與事件監聽器之間的對應關系,如果某一事件發生,可以從對應關系中查找出應該將該事件派發至哪些事件監聽器。
2.1.2 事件(Event)與事件監聽器(EventListener)
EventBus並不強制要求事件(Event)與事件監聽器(EventListener)必須繼承特定的類或實現特定的接口,普通的Java類即可。這是因為事件(Event)就是一個對象,它保存着特定時間點的特定狀態,而事件監控器(EventListener)實質就是一個方法(Method),即發生特定事件就執行該方法,所以理論上這兩者可以是任意的普通類。那么EventBus使用什么策略從一個普通的Java類中識別出事件(Event)與事件監聽器(EventListener),從而維護它們之間的對應關系?既然是一個普通的Java類,那么策略應該是多種多樣的,EventBus為此設計了一個策略接口:HandlerFindingStrategy,如下圖所示:

這里首先說明一下EventHandler(事件處理器)。
如上所述,事件監控器(EventListener)實質就是一個方法(Method),而方法的調用需要目標對象target、目標方法method的共同參與,EventHandler對這兩者信息進行了封裝,后續討論皆以事件處理器(EventHandler)表示事件監聽器(EventListener)。
HandlerFindingStrategy僅僅有一個方法:Multimap<Class<?>, EventHandler> findAllHandlers(Object source),它代表着策略的抽象過程:從傳入的類實例對象source中尋找出所有的事件(Event)與事件處理器(EventHandler)的對應關系。注意,該方法的返回值為Multimap,這是一種特殊的Map,一個鍵可以對應着多個值,它表示一個事件可以有多個事件處理器與之對應;其中,鍵為事件對象類實例,值為事件處理器實例。
目前,EventBus僅僅提供一種HandlerFindingStrategy的實現:AnnotatedHandlerFinder,它是一種基於注解(Annotation)的實現,

以類實例對象listener為例說明一下工作過程:
(1)獲取實例對象listener的類實例clazz;
(2)獲取類實例clazz的所有方法,並依次迭代處理,假設其中的一個方法為method:
a. 如果method標記有注解“Subscribe”,且method只有一個參數,則表示method可以作為事件監聽器,繼續處理;否則繼續處理下一個method;
b. method的這個參數類型即為事件類型eventType;
c. 通過makeHandler()將實例對象listener、方法method封裝為handler(事件處理器);
d. 維護eventType、handler之間的對應關系,將其保存至methodsInListener;
(3)獲取類實例clazz的父類實例,將其保存至clazz,如果clazz不為null,則繼續(2);否則結束;
makeHandler()工作過程實際就是構建EventHandler對象,如下所示:

如果方法method標記有注解AllowConcurrentEvents,則表示該方法可以被事件處理器在多線程環境下線程安全的訪問,直接使用EventHandler封裝即可;如果方法method沒有標記有注解AllowConcurrentEvents,則表示該方法無法被事件處理器在多線程環境下線程安全的訪問,需要使用SynchronizedEventHandler封裝。SynchronizedEventHandler繼承自EventHandler,僅有一處不同:

即使用關鍵字synchronized修飾方法handleEvent,使其可以在多線程環境下被安全地訪問。
有幾點需要注意:
(1)事件與事件處理器之間的對應關系是依靠事件類型(eventType)連接起來的,而事件類型(eventType)就是事件監聽器方法的參數類型;
(2)類實例對象listener的任何一個方法,只要它含有注解Subscribe且只有一個參數,就可以作為事件監聽器或事件處理器;
(3)類實例對象的所有父類都會參與上述工作過程;
2.1.3 register

handlersByType:SetMultimap實例,用於維護EventBus內部所有的事件與事件處理器的對應關系(SetMultimap、Multimap的使用方法可以參考Google Guava的相關文檔);
finder:AnnotatedHandlerFinder實例;
object:類實例對象,用於從中尋找出事件與事件處理器的對應關系;handlersByType某一事件對應的事件處理器可能來自於不同的類實例對象object;
2.2 EventBus unregister
EventBus unregister就是從handlersByType中移除類實例對象object中包含的所有事件與事件處理器的對應關系,工作過程比較簡單,不再贅述。

2.3 EventBus post
EventBus post大致可以分為以下三個過程:

2.3.1 flattenHierarchy
EventBus post event時,event整個繼承關系樹中所有類和接口對應的事件處理器都會參考到事件派發的過程中來,flattenHierarchy就是用於獲取event整個繼承關系樹中所有類和接口的類實例的,每一個類實例(Class)表示一個事件類型:

因為這個繼承關系樹在系統(進程)的運行過程中不會發生變化(不考慮熱加載的情況),這里使用了緩存技術,用於緩存某個對象的繼承關系樹,使用Google Guava LoadingCache構建,我們不對此詳細展開討論,僅僅闡述緩存沒有命中時的處理情況:

可以看出,整個繼承關系樹中的類和接口都被獲取。
2.3.2 enqueueEvent
依次為每個事件類型對應的事件處理器派發事件,此時事件處理器並沒有被實際執行,而是以EventWithHandler對象的形式被存入一個隊列。

getHandlersForEventType:用於獲取事件類型對應的所有事件處理器;
enqueueEvent:用於將事件(Event)和事件處理器(EventHandler)封裝為EventWithHandler放入隊列;
EventWithHandler如下:

enqueueEvent如下:

eventsToDispatch是一個ThreadLocal<ConcurrentLinkedQueue<EventWithHandler>>變量,也就是每一個post線程內部都有一個隊列,用於存放EventWithHandler對象。
疑問:是否需要使用ConcurrentLinkedQueue?
2.3.3 dispatchQueuedEvents
dispatchQueuedEvents的過程其實就是執行隊列中的事件處理器,過程如下:


可以看出,隊列中的事件處理器是依次被執行的。
疑問:是否需要使用isDispatching?
EventBus是一種同步實現(即事件處理器是被依次觸發的),另外有一種異步實現AsyncEventBus,核心原理相同,有興趣的讀者可自行研究。
3. Guava EventBus使用示例

輸出結果:
EventHandler handle Event
EventHandler2 handle Event2
分類:
Design Patterns, Java