Google-Guava-EventBus源码解读
生活随笔
收集整理的這篇文章主要介紹了
Google-Guava-EventBus源码解读
小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
Guava是Google開源的一個Java基礎(chǔ)類庫,它在Google內(nèi)部被廣泛使用。Guava提供了很多功能模塊比如:集合、并發(fā)庫、緩存等,EventBus是其中的一個module,本篇結(jié)合EventBus源碼來談?wù)勊脑O(shè)計與實現(xiàn)。
概要
首先,我們先來預(yù)覽一下EventBus模塊的全部類圖:
類并不是多而且?guī)缀鯖]有太多繼承關(guān)系。
下面,我們來看一下各個類的職責(zé):
- EventBus:核心類,代表了一個事件總線。Publish事件也由它發(fā)起。
- AsyncEventBus:在分發(fā)事件的時候,將其壓入一個全局隊列的異步分發(fā)模式。
- Subscriber:對某個事件的處理器抽象,封裝了事件的訂閱者以及處理器,并負(fù)責(zé)事件處理(該類的類名及其語義有些不明確,后續(xù)會談到)。
- SubscriberRegistry:訂閱注冊表,它用于存儲Subscriber跟Event的對應(yīng)關(guān)系,以便于EventBus在publish一個事件時,可以找到它對應(yīng)的Subscriber。
- Dispatcher:事件分發(fā)器,它定義了事件的分發(fā)策略。
- @Subscribe:用于標(biāo)識事件處理器的注解,當(dāng)EventBus publish一個事件后,相應(yīng)的Subscriber將會得到通知并執(zhí)行事件處理器。
- @AllowConcurrentEvents:該注解跟@Subscribe一同使用,標(biāo)識該訂閱者的處理方法為線程安全的,該注解還用于標(biāo)識該方法將可能會被EventBus在多線程環(huán)境下執(zhí)行。
- DeadEvent:死信(沒有訂閱者關(guān)注的事件)對象。
- SubscribeExceptionHandler:訂閱者拋出異常的處理器。
- SubscribeExceptionContext:訂閱者拋出異常的上下文對象。
分“類”解讀
EventBus
它有這么幾個字段:- identifier:事件總線的標(biāo)識,這說明在一個應(yīng)用里是可以有多個EventBus的。如果不指明它的值,它將以“default”作為其默認(rèn)名稱。
- executor:它是Executor接口的實例,用于對訂閱者處理事件方法的執(zhí)行。這里需要注意的是,該字段的實例化是在EventBus內(nèi)部構(gòu)造器中,并不是從外部注入進(jìn)來的,另外真正的執(zhí)行訂閱者方法的時機(jī)也不由EventBus負(fù)責(zé),而是由Subscriber負(fù)責(zé),因此該字段會被公開給外部訪問。
- exceptionHandler:它是SubscribeExceptionHandler的實例,用于處理訂閱者在執(zhí)行事件處理方法時拋出的異常。EventBus可以接收一個外部定義的異常處理器,也可以采用內(nèi)部缺省的日志記錄處理器。
- subscribers:訂閱者注冊表,用于存儲所有的事件以及事件處理器、訂閱對象的對應(yīng)關(guān)系。
- dispatcher:事件分發(fā)器,用于分發(fā)事件給訂閱對象的事件處理器,該對象在EventBus構(gòu)造方法內(nèi)部初始化,默認(rèn)的實現(xiàn)是PerThreadQueuedDispatcher,該分發(fā)器將事件存入隊列,并保證在同一個線程上發(fā)送的事件能夠按照他們發(fā)布的順序被分發(fā)給所有的訂閱者。
- register:注冊subscriber;
- unregister:移除注冊過的subscriber;
- post:發(fā)布事件;
你可以將EventBus看做是一個代理,這些方法真正的實現(xiàn)者都是上面的這些對象。
AsyncEventBus
一個支持異步發(fā)布模式的EventBus,它覆蓋了EventBus的默認(rèn)構(gòu)造方法,指定了一個異步的分發(fā)器:LegacyAsyncDispatcher,這個分發(fā)器基于一個全局的隊列來暫存未發(fā)布的事件。Subscriber
之前也提到Subscriber的名稱是比較容易混淆的。這個類的名稱看似表示一個訂閱者對象,但其實是用來封裝“一個訂閱者的一個事件處理器”對象。因為當(dāng)一個訂閱者存在多個處理方法被標(biāo)注為@Subscribe的時候,那么每個處理方法都對應(yīng)于一個獨立的Subscriber對象的實例。我個人覺得這個名稱與其具體的實現(xiàn)語義有些混淆。當(dāng)然也許實現(xiàn)者認(rèn)為:一個對象以及一個事件處理器就是一個Subscriber的話,那是沒有問題的。因此這里為了理解方便,你可以將其看做是一個封裝了訂閱者對象以及一個訂閱者處理器方法的實體類。Subscriber的訪問級別是package的,它還承擔(dān)了執(zhí)行事件處理的責(zé)任。通過一個create靜態(tài)工廠方法創(chuàng)建它:
static Subscriber create(EventBus bus, Object listener, Method method) {return isDeclaredThreadSafe(method)? new Subscriber(bus, listener, method): new SynchronizedSubscriber(bus, listener, method);}
它接收三個參數(shù):
- bus:EventBus的實例,通過它來獲取事件的執(zhí)行器(executor)
- listener:真實的訂閱者對象
- method:訂閱對象的事件處理方法的Method實例
該類的兩個關(guān)鍵方法之一:
dispatchEvent:
final void dispatchEvent(final Object event) {executor.execute(new Runnable() {@Overridepublic void run() {try {invokeSubscriberMethod(event);} catch (InvocationTargetException e) {bus.handleSubscriberException(e.getCause(), context(event));}}});}
它調(diào)用一個多線程執(zhí)行器來執(zhí)行事件處理器方法。
另一個方法:invokeSubscriberMethod以反射的方式調(diào)用事件處理器方法。
另外,該類對Object的equals方法進(jìn)行了override并標(biāo)識為final。主要是為了避免同一個對象對某個事件進(jìn)行重復(fù)訂閱,在SubscriberRegistry中會有相應(yīng)的判等操作。當(dāng)然這里Subscriber也override并final了hashCode方法。這是最佳實踐,不必多談,如果不了解的可以去看看《Effective Java》。
該類還有個內(nèi)部類,就是我們上面談到的SynchronizedSubscriber,它繼承了Subscriber,與Subscriber唯一的不同就是在invokeSubscriberMethod的執(zhí)行上做了同步。
SubscriberRegistry
針對單個EventBus的訂閱與事件的關(guān)系維護(hù)。在內(nèi)部用來存儲訂閱者關(guān)系的對象是java并發(fā)包下的并發(fā)Map:ConcurrentMap,該map以Class對象為鍵,值的類型是CopyOnWriteArraySet<Subscriber>集合類型。SubscriberRegistry直接依賴EventBus對象,所以在構(gòu)造器中需要注入EventBus的實例。
SubscriberRegistry里有兩個關(guān)鍵的實例方法:register/unregister。
register
接收訂閱者對象作為參數(shù)并建立Event跟Subscriber的關(guān)聯(lián)關(guān)系。我們來看看它的實現(xiàn):
void register(Object listener) {Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);for (Map.Entry<Class<?>, Collection<Subscriber>> entry : listenerMethods.asMap().entrySet()) {Class<?> eventType = entry.getKey();Collection<Subscriber> eventMethodsInListener = entry.getValue();CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);if (eventSubscribers == null) {CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();eventSubscribers = MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);}eventSubscribers.addAll(eventMethodsInListener);}}
它首先獲得一個Multimap實例(它是Google Guava集合框架提供的一個多值Map類型,也就是說一個key可以對應(yīng)多個value),該Multimap用于存儲事件類型對應(yīng)的該訂閱者內(nèi)所有關(guān)于該事件的處理器方法集合,其key為事件的Class類型。這里在for循環(huán)的中通過asMap獲取其map視圖,即可將Multimap對應(yīng)的多個值存儲到一個Collection中。
也就是說這里for循環(huán)的每個entry,表示的是一個事件的Class實例對應(yīng)的一組Subscriber的集合,即eventMethodsInListener。
然后根據(jù)該事件的Class對象從注冊表中獲取對應(yīng)的存儲Subscriber實例的集合,如果不存在則創(chuàng)建該集合,然后將該訂閱者內(nèi)所有的事件處理器方法都加入到注冊表中去。
unregister
unregister的實現(xiàn)跟register有些類似,先查找該訂閱者所有的事件類型與處理器的對應(yīng)關(guān)系。然后,遍歷所有的事件類型,移除針對當(dāng)前訂閱者的所有Subscriber實例。findAllSubscribers
register/unregister方法都調(diào)用了findAllSubscribers方法,它有一些特別之處,這里需要單獨拎出來提一下。findAllSubscribers用于查找事件類型以及事件處理器的對應(yīng)關(guān)系。查找注解需要涉及到反射,通過反射來獲取標(biāo)注在方法上的注解。因為Guava針對EventBus的注冊采取的是“隱式契約”而非接口這種“顯式契約”。而類與接口是存在繼承關(guān)系的,所有很有可能某個訂閱者其父類(或者父類實現(xiàn)的某個接口)也訂閱了某個事件。因此這里的查找需要順著繼承鏈向上查找父類的方法是否也被注解標(biāo)注,代碼實現(xiàn):
private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();Class<?> clazz = listener.getClass();for (Method method : getAnnotatedMethods(clazz)) {Class<?>[] parameterTypes = method.getParameterTypes();Class<?> eventType = parameterTypes[0];methodsInListener.put(eventType, Subscriber.create(bus, listener, method));}return methodsInListener;}
同樣涉及這個問題的,還有根據(jù)事件類型獲取Subscriber實例的方法:getSubscribers。
getSubscribers
Iterator<Subscriber> getSubscribers(Object event) {ImmutableSet<Class<?>> eventTypes = flattenHierarchy(event.getClass());List<Iterator<Subscriber>> subscriberIterators =Lists.newArrayListWithCapacity(eventTypes.size());for (Class<?> eventType : eventTypes) {CopyOnWriteArraySet<Subscriber> eventSubscribers = subscribers.get(eventType);if (eventSubscribers != null) {// eager no-copy snapshotsubscriberIterators.add(eventSubscribers.iterator());}}return Iterators.concat(subscriberIterators.iterator());}Dispatcher
dispatcher用于分發(fā)事件給Subscriber。它內(nèi)部實現(xiàn)了多個分發(fā)器用于提供在不同場景下不同的事件順序性。Dispatcher是一個抽象類,定義了一個核心抽象方法: abstract void dispatch(Object event, Iterator<Subscriber> subscribers);該方法用于將一個指定的事件分發(fā)給所有的訂閱者。
另外在Dispatcher提供了三個不同的分發(fā)器實現(xiàn):
PerThreadQueuedDispatcher
它比較常用,針對每個線程構(gòu)建一個隊列用于暫存事件對象。保證所有的事件都按照他們publish的順序從單一的線程上發(fā)出。保證從單一線程上發(fā)出,沒什么特別的地方,主要是在內(nèi)部定義了一個隊列,將其放在ThreadLocal中,用以跟特定的線程關(guān)聯(lián)。
LegacyAsyncDispatcher
另一個異步分發(fā)器的實現(xiàn):LegacyAsyncDispatcher,之前在介紹AsyncEventBus的時候提到,它就是用這種實現(xiàn)來分發(fā)事件。它在內(nèi)部通過一個ConcurrentLinkedQueue<EventWithSubscriber>的全局隊列來存儲事件。從關(guān)鍵方法:dispatch的實現(xiàn)來看,它跟PerThreadQueuedDispatcher的區(qū)別主要是兩個循環(huán)上的差異(這里基于隊列的緩存事件的方式,肯定會存在兩個循環(huán):循環(huán)取隊列里的事件以及循環(huán)發(fā)送給Subscriber)。
PerThreadQueuedDispatcher:是兩層嵌套循環(huán),外層是遍歷隊列取事件,內(nèi)存是遍歷事件的訂閱處理器。
LegacyAsyncDispatcher:是一前一后兩個循環(huán)。前面一個是遍歷事件訂閱處理器,并構(gòu)建一個事件實體對象存入隊列。后一個循環(huán)是遍歷該事件實體對象隊列,取出事件實體對象中的事件進(jìn)行分發(fā)。
ImmediateDispatcher
其實以上兩個基于中間隊列的分發(fā)實現(xiàn)都可以看做是異步模式,而ImmediateDispatcher則是同步模式:只要有事件發(fā)生就會立即分發(fā)并被立即得到處理。ImmediateDispatcher從感官上看類似于線性并順序執(zhí)行,而采用隊列的方式有多線程匯聚到一個公共隊列的由發(fā)散到聚合的模型。因此,ImmediateDispatcher的分發(fā)方式是一種深度優(yōu)先的方式,而使用隊列是一種廣度優(yōu)先的方式。DeadEvent
它是一個實體對象,封裝了沒有訂閱者的事件。DeadEvent由兩個屬性組成:- source:事件源(通常指發(fā)布事件的EventBus對象)
- event:事件對象
總結(jié)
Guava的EventBus源碼還是比較簡單、清晰的。從源碼來看,它一番常用的Observer的設(shè)計方式,放棄采用統(tǒng)一的接口、統(tǒng)一的事件對象類型。轉(zhuǎn)而采用基于注解掃描的綁定方式。其實無論是強(qiáng)制實現(xiàn)統(tǒng)一的接口,還是基于注解的實現(xiàn)方式都是在構(gòu)建一種關(guān)聯(lián)關(guān)系(或者說滿足某種契約)。很明顯接口的方式是編譯層面上強(qiáng)制的顯式契約,而注解的方式則是運行時動態(tài)綁定的隱式契約關(guān)系。接口的方式是傳統(tǒng)的方式,編譯時確定觀察者關(guān)系,清晰明了,但通常要求有一致的事件類型、方法簽名。而基于注解實現(xiàn)的機(jī)制,剛好相反,編譯時因為沒有接口的語法層面上的依賴關(guān)系,顯得不那么清晰,至少靜態(tài)分析工具很難展示觀察者關(guān)系,但無需一致的方法簽名、事件參數(shù),至于多個訂閱者類之間的繼承關(guān)系,可以繼承接收事件的通知,可以看作既是其優(yōu)點也是其缺點。
原文發(fā)布時間為:2015-06-01
本文作者:vinoYang
本文來自云棲社區(qū)合作伙伴CSDN博客,了解相關(guān)信息可以關(guān)注CSDN博客。
總結(jié)
以上是生活随笔為你收集整理的Google-Guava-EventBus源码解读的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 清晨做梦梦到蛇是什么意思
- 下一篇: 晚上梦到猫预示着什么