本文分爲三個部分:
- 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