優秀開源代碼解析(一)Google guava之EventBus

Java極客  |  做者  /  鏗然一葉
這是Java極客的第 49 篇原創文章

一、學習開源代碼的收穫

學習開源代碼能夠有如下收穫:java

1.學些其架構設計思想,看看怎麼實現一個高可用,可擴展的架構。git

2.學習一些好的java語法,畢竟你在實際代碼過程當中不會使用到全部的java語法,而在看源碼的過程當中,就有可能發現你不曾使用過,但比較巧妙的用法。github

3.學習設計模式,開源代碼經常會使用到一些設計模式,能夠加深你對設計模式的理解。編程

4.學習一些基本設計原則,好比經過有界隊列避免內存溢出,經過異步線程提升性能,經過依賴注入提升擴展性,可測性等等。設計模式

二、EventBus是什麼

EventBus 是Google的一個開源庫,它利用發佈/訂閱者者模式來對項目進行解耦。它能夠利用不多的代碼,來實現多組件間通訊。架構

三、EventBus代碼結構

EventBus代碼結構以下:併發

類說明:異步

3.一、EventBus

EventBus是核心入口類,若是全都採用默認實現,只須要實現Listener,並經過調用EventBus類的方法則可完成全部功能。ide

3.二、SubscriberExceptionHandler

SubscriberExceptionHandler是異常處理接口,可替換本身的實現。post

3.三、Executor

Executor用於異步執行Listener的監聽方法,可替換本身的實現。

3.四、Dispatcher

Dispatcher是event派發接口,可替換本身的實現,默認提供了3個實現類。

3.五、SubscriberRegistry

SubscriberRegistry是事件註冊類,也用於獲取訂閱者。

3.六、Subscriber

Subscriber是訂閱者,對Listener作了封裝,屏蔽了複雜的調用邏輯,使得使用者沒必要關心這些複雜邏輯,只要提供Listener的具體實現則可。

3.七、SynchronizedSubscriber

SynchronizedSubscriber是支持併發調用的訂閱者,能夠經過在Listener的事件監聽方法上添加AllowConcurrentEvents註解來達到使用SynchronizedSubscriber的目的。

3.八、Subscribe

Subscribe是一個註解類,能夠在任何類的方法上添加該註解來表達該方法是一個事件監聽方法。

3.九、DeadEvent

DeadEvent用於記錄那些已經沒有訂閱者的事件。

3.十、SubscriberExceptionContext

SubscriberExceptionContext是異常上下文,用於當訂閱者處理異常時記錄相關的上下文信息,方便異常處理實現類得到這些信息來處理異常。

四、EventBus的優秀之處

1.面向接口編程:Executor,Dispatcher,SubscriberExceptionHandler都是接口,使用者能夠替換具體的實現類。

2.使用依賴注入,使得可測性加強。從圖中能夠看到EventBus持有Executor,Dispatcher,SubscriberExceptionHandler三個對象,它們可經過EventBus的構造器注入,這樣使用者就有機會方便的替換具體的實現,或者mock一個對象來用於測試。若是它們不是可注入的,而是直接在某個方法內調用,就失去了替換的機會。固然,接口注入的方式還有不少,例如經過set方法,經過反射動態生成等等,但構造器注入是最簡單,最省心的方法。

3.異步處理,提升性能。Subscriber最終處理event是經過Executor以異步線程方式執行,不會由於同步而阻塞,大大提升了性能。

4.利用Subscribe註解+反射,使得任何類的任何方法均可以成爲事件監聽類,而不須要實現特定的監聽者接口。

5.考慮了異常處理機制,一方面提供了SubscriberExceptionHandler接口讓使用者來實現具體的處理邏輯,另一方面提供了DeadEvent類,將那些失去訂閱者的事件統一歸類到DeadEvent,由使用者自行實現一個Listener去處理它們。

6.經過模板方法固化了整個調用流程,使用者只須要按照要求簡單實現則可以使用。

五、EventBus具體使用過程

5.一、實現一個Listener

5.1.一、使用說明

Listener是一個事件監聽類,一個Listener類能夠經過不一樣的方法同時監聽多個事件,任何類均可以做爲Listener,但須要遵循如下要求:

1.必須在監聽方法上添加Subscribe註解,用以表達該方法是一個監聽方法

2.此監聽方法只能有一個參數,這個參數就是要監聽的事件,參數類的Class能夠理解爲就是要監聽的EventType

5.1.二、相關代碼

1.Listener例子

public class MyListener {

  // 添加Subscribe註解則表示要監聽某個事件
  @Subscribe
  public void onEvent(MyEvent1 event1) {
    // do something
  }
  
  // 一個Listener能夠監聽多個事件
  @Subscribe
  public void onEvent(MyEvent2 event2) {
    // do something
  }
}
複製代碼

2.SubscriberRegistry.java:

// 傳入的clazz類就是Listener的Class
  private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
    Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
    Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
    for (Class<?> supertype : supertypes) {
      for (Method method : supertype.getDeclaredMethods()) {
        // 這裏查找方法上是否有Subscribe註解
        if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
          // TODO(cgdecker): Should check for a generic parameter type and error out
          Class<?>[] parameterTypes = method.getParameterTypes();
          // 這裏檢查方法的參數只有1個
          checkArgument(
              parameterTypes.length == 1,
              "Method %s has @Subscribe annotation but has %s parameters."
                  + "Subscriber methods must have exactly 1 parameter.",
              method,
              parameterTypes.length);

          MethodIdentifier ident = new MethodIdentifier(method);
          if (!identifiers.containsKey(ident)) {
            identifiers.put(ident, method);
          }
        }
      }
    }
    return ImmutableList.copyOf(identifiers.values());
  }
複製代碼

5.二、構造EventBus

5.2.一、使用說明

1.在一個系統中,根據用途不一樣,能夠同時存在多個EventBus,不一樣的EventBus經過identifier來識別。

2.爲方便使用,EventBus提供了多個構造器,使用者能夠根據須要注入不一樣的實現類,最簡單的構造器是一個無參構造器,所有使用默認實現。

3.在實際使用過程當中,可使用一個單例類來持有EventBus實例,若有須要,能夠持有不一樣的EventBus實例用於不一樣的用途。

5.2.二、相關代碼

1.EventBus.java

//無參構造器
  public EventBus() {
    this("default");
  }
  
  //指定標識符構造器
  public EventBus(String identifier) {
    this(
        identifier,
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        LoggingHandler.INSTANCE);
  }
  
  //注入自定義異常類構造器
  public EventBus(SubscriberExceptionHandler exceptionHandler) {
    this(
        "default",
        MoreExecutors.directExecutor(),
        Dispatcher.perThreadDispatchQueue(),
        exceptionHandler);
  }
  
  //注入全部參數構造器,需注意的是,此方法不是public的,只能在包內訪問。
  EventBus(
      String identifier,
      Executor executor,
      Dispatcher dispatcher,
      SubscriberExceptionHandler exceptionHandler) {
    this.identifier = checkNotNull(identifier);
    this.executor = checkNotNull(executor);
    this.dispatcher = checkNotNull(dispatcher);
    this.exceptionHandler = checkNotNull(exceptionHandler);
  }
複製代碼

5.三、註冊Listener

5.3.一、使用說明

以上兩步完成後,便可經過EventBus來註冊Listener。

5.3.二、相關代碼

1.EventBus.java

private final SubscriberRegistry subscribers = new SubscriberRegistry(this);
  
  public void register(Object object) {
    subscribers.register(object);
  }
複製代碼

2.SubscriberRegistry.java

void register(Object listener) {
    // 查找有Subscribe註解的方法,並封裝爲Subscriber,Multimap的key記錄的Class就是要監聽的對象的Class
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    for (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<>();
        eventSubscribers =
            MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
      }

      eventSubscribers.addAll(eventMethodsInListener);
    }
  }
複製代碼

5.四、發佈Event

5.4.一、使用說明

發佈事件比較簡單,只要在須要發佈事件的地方獲得EventBus實例,而後調用post方法則可。

5.4.二、相關代碼

1.EventBus.java

public void post(Object event) {
    // 根據event找到訂閱者,這裏實際是根據event.Class來查找,也即和Listener的監聽方法的參數的Class一致。
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    if (eventSubscribers.hasNext()) {
      //經過dispatcher來派發事件,最終調用的是Subscriber的dispatchEvent方法
      dispatcher.dispatch(event, eventSubscribers);
    } else if (!(event instanceof DeadEvent)) {
      // the event had no subscribers and was not itself a DeadEvent
      post(new DeadEvent(this, event));
    }
  }
複製代碼

2.Subscriber.java

final void dispatchEvent(final Object event) {
    // 經過executor來實現異步調用,這個executor在EventBus是可注入的,能夠注入修改後的實現類
    executor.execute(
        new Runnable() {
          @Override
          public void run() {
            try {
              invokeSubscriberMethod(event);
            } catch (InvocationTargetException e) {
              // 這裏最終調用的是在EventBus中注入的SubscriberExceptionHandler,能夠注入修改後的實現類
              bus.handleSubscriberException(e.getCause(), context(event));
            }
          }
        });
  }
  
  void invokeSubscriberMethod(Object event) throws InvocationTargetException {
    try {
      //這裏的method就是Listener的監聽方法,target就是Listener對象,event就是這個監聽方法的入參
      method.invoke(target, checkNotNull(event));
    } catch (IllegalArgumentException e) {
      throw new Error("Method rejected target/argument: " + event, e);
    } catch (IllegalAccessException e) {
      throw new Error("Method became inaccessible: " + event, e);
    } catch (InvocationTargetException e) {
      if (e.getCause() instanceof Error) {
        throw (Error) e.getCause();
      }
      throw e;
    }
  }
複製代碼

六、Google guava源碼地址

github.com/google/guav…

end.


Java極客站點: javageektour.com/

相關文章
相關標籤/搜索