【源碼閱讀】EventBus源碼閱讀

EventBus 是一個實現了訂閱者/發佈者模式的框架。在閱讀EventBus 之前,我們先來看一下EventBus如何使用。

  • EventBus 使用例子
public interface Report {
    // 上報信息
    void report (String msg);
}


public SmsReport implements Report {
    @Subscribe
    @Override
    public void report (String msg) {
        System.out.println("send " + msg + " by sms");
    }
}


public class App {
    public static void main (String[] args) {
        Report report = new SmsReport();

        // 創建EventBus 實例
        EventBus eventBus = new EventBus();

        // 註冊訂閱者
        eventBus.register(report);

        // 發佈消息
        String msg = "有內鬼,取消交易!";
        eventBus.port(msg);
   }
}


        

 

EventBus 使用是比較簡單的。從上面我們可以知道,加了Subscribe 註解的方法相當於訂閱者。 EventBus提供了register 函數,給訂閱者進行註冊。另外提供了 post函數,即發佈消息。

我們回過頭來,看一下訂閱者/發佈者模式的實現方式。然後再來看一下EventBus 是如何實現的。

  • 訂閱者/發佈者模式實現
public interface ISubscriber {
    // 處理消息
    void handle (String msg);
}


public interface IPublisher {
    // 註冊
    void register (ISubscriber subscriber);

    // 發佈消息
    void post(String msg);
}


public class SubscriberA implements ISubsciber {
    @Override
    public void handle (String msg) {
        System.out.println(msg);
    }
}


public class PublisherCenter implements IPublisher {
    // 訂閱者列表
    private List<ISubscriber> subscriberList = new ArrayList<>();


    @Override
    public void register (ISubscriber subscriber) {
        subscriberList.add(subscriber);
    }


    @Override
    public void post (String msg) {
        subscriberList.foreach(subscriber -> {
            subscriber.handle(msg);
        });
    }
}

public class App {
    public static void main (String[] args) {
        //訂閱者
        ISubscriber subscriber = new SubscriberA();

        IPublisher pub = new PublisherCenter();
        
        // 註冊
        pub.register(subscriber);
        
        // 發佈消息
        String msg = "有內鬼,取消交易!";
        pub.post(msg);
    }
}

 

從這裏,我們可以看出。自己現實的訂閱者/發佈者模式,在使用上是和EventBus 是比較相似的。不同的地方是,EventBus通過Subscribe註解來表示訂閱者。

從自己的實現上來看,在發佈者中是需要維護一張訂閱者列表的,即PublishCenter中的subscriberList。另外,在PublishCenter發佈消息時,會遍歷訂閱者列表,把消息發給每一個訂閱者進行處理。到這裏,我們大概知道訂閱者/發佈者模式的實現原理,那麼,我們可以看看EventBus中是如何進行實現的。

 

EventBus 源碼閱讀

 

  • EventBus 類結構

從EventBus 成員以及方法命名來看。我們可以猜測SubscriberRegistry 相當於我們自己現實中的訂閱者列表 subscriberList, EventBus 這裏應該是進行了一個封裝。

register 和 post 函數,和我們自己現實的一樣。一個應該是用於註冊訂閱者,一個用於發佈消息。

 

  • register 註冊函數

我們可以從register 入手,看下EventBus 是如何進行訂閱者註冊的。

// EventBus.regiser
public void register(Object object) {
    // 調用了SubscriberRegistry 的方法register
    subscribers.register(object);
}


// SubscribeRegistry.register
void register(Object listener) {
    // 找出所有訂閱者
    Multimap<Class<?>, Subscriber> listenerMethods = findAllSubscribers(listener);

    // 把新的訂閱者添加到subscribers中
    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);
    }
}

從上面的代碼中,我們可以知道,EventBus把註冊的行爲委託給了SubscribeRegistry 進行註冊。在SubscribeRegister的註冊函數中。有幾個點我們是可以進行了解的。

 

第一: findAllSubscribers 函數做了什麼,listenerMethods 這個數據表示什麼?

private Multimap<Class<?>, Subscriber> findAllSubscribers(Object listener) {
    Multimap<Class<?>, Subscriber> methodsInListener = HashMultimap.create();
    // 獲取lintener類信息
    Class<?> clazz = listener.getClass();
    
    // 獲取加了Subscribe 註解的方法
    for (Method method : getAnnotatedMethods(clazz)) {
        // 獲取方法的參數類型
        Class<?>[] parameterTypes = method.getParameterTypes();
        Class<?> eventType = parameterTypes[0];
        
        // 存放參數類型->訂閱者映射信息
        methodsInListener.put(eventType, Subscriber.create(bus, listener, method));
    }
    return methodsInListener;
}

從上面的代碼中,我們可以知道,findAllSubscribers 主要是通過反射獲取訂閱者。返回訂閱者訂閱的消息類型和訂閱者的映射關係數據。

 

第二: CopyOnWriteArraySet 數據結構。這裏用了CopyOnWriteArraySet 來存放訂閱者,這比起常見的ArrayList、HashSet這些好什麼好處?

CopyOnWriteArraySet 主要是採用了寫時複製的做法。不在原數組上進行增刪操作。而是複製元數組,在複製的數組上進行增刪操作。當操作完後,把原數組的指針指到新數組上即可。

這樣做的目的主要是爲了提高併發讀的性能(不需要加鎖)。

 

到這裏,我們基本知道EventBus是如何進行訂閱者註冊的了:

當用戶進行訂閱者註冊的時候,EventBus把註冊行爲委託給SubscribeRegistry 進行註冊(SubscribeRegistry相當於我們自己現實的PublishCenter)。

在SubscibeRegistry中,會通過反射獲取到listener上哪些方法加了Subscibe註解。提取這些方法封裝爲訂閱者。然後存放到訂閱者列表(subscribes)中。這裏訂閱者列表的數據結構是Map的。 key 是消息類型, value是訂閱key的訂閱者列表。

private final ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers;

 

  • post 函數

post 函數,發佈消息。從我們自己的現實中,我們可以知道。當發佈消息時,會遍歷訂閱者列表,並把消息發佈給訂閱者。我們這裏看下EventBus 中post做了什麼。

public void post(Object event) {
    // 取出訂閱事件event的所有訂閱者(迭代器)
    Iterator<Subscriber> eventSubscribers = subscribers.getSubscribers(event);
    
    // 分發執行
    if (eventSubscribers.hasNext()) {
        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));
    }
}

 

這裏post函數,跟我們自己PublishCenter中實現的類似。都是取出訂閱者,然後把消息發給訂閱者進行執行。不過EventBus這裏把分發消息的行爲進行了抽象封裝。

這裏可以根據不同的場景,實現不同的dispatcher。EventBus提供了三種實現。

ImmediateDispatcher:  同步執行,與我們PublishCenter中的一樣。

void dispatch(Object event, Iterator<Subscriber> subscribers) {
  checkNotNull(event);
  while (subscribers.hasNext()) {
    subscribers.next().dispatchEvent(event);
  }
}

 

LegacyAsyncDispatcher: 異步執行,把事件和訂閱者進行封裝,放到一個全局的阻塞隊列中。之後從隊列中取訂閱者進行執行。這裏如果有多個線程進行發佈消息。那麼不保證按提交的順序進行執行。比如有兩個發佈者線程A、B同時發佈了消息。那麼這裏發佈者線程A、B需要通知的訂閱者是放到了同一個阻塞隊列中的。發佈者線程A和B再從阻塞隊列中取訂閱者進行處理。

private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
    Queues.newConcurrentLinkedQueue();

@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
  checkNotNull(event);
  while (subscribers.hasNext()) {
    queue.add(new EventWithSubscriber(event, subscribers.next()));
  }

  EventWithSubscriber e;
  while ((e = queue.poll()) != null) {
    e.subscriber.dispatchEvent(e.event);
  }
}

 

PerThreadQueueDispatcher: 在提交的發佈者線程中進行執行,這裏同樣採用了隊列來存放訂閱者。與LegacyAsyncDispatcher不同的是,PerThreadQueueDispatcher只發布自己的事件。不同發佈者線程之間互不影響。(這裏主要是通過ThreadLocal來實現的)。

/** Per-thread queue of events to dispatch. */
private final ThreadLocal<Queue<Event>> queue =
    new ThreadLocal<Queue<Event>>() {
      @Override
      protected Queue<Event> initialValue() {
        return Queues.newArrayDeque();
      }
    };

/** Per-thread dispatch state, used to avoid reentrant event dispatching. */
private final ThreadLocal<Boolean> dispatching =
    new ThreadLocal<Boolean>() {
      @Override
      protected Boolean initialValue() {
        return false;
      }
    };

@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
  checkNotNull(event);
  checkNotNull(subscribers);
  Queue<Event> queueForThread = queue.get();
  queueForThread.offer(new Event(event, subscribers));

  if (!dispatching.get()) {
    dispatching.set(true);
    try {
      Event nextEvent;
      while ((nextEvent = queueForThread.poll()) != null) {
        while (nextEvent.subscribers.hasNext()) {
          nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
        }
      }
    } finally {
      dispatching.remove();
      queue.remove();
    }
  }
}

 

到這裏,我們大概知道了EventBus中post是如何做的了。EventBus發佈消息時,會取出消息上註冊的訂閱者。然後採取不同的分發策略,把消息發給訂閱者進行執行。

 

  • 其他知識點

在EventBus 中,我們可以看到很多設計模式的例子。比如枚舉單例、委託等等。另外也有一些其他的知識點,比如反射、CopyOnWrite等等。

 

下面就是枚舉單例的用法,這裏實現了Executor接口。在執行訂閱者邏輯時,根據不同場景,也是可以有不同執行策略的。比如同步執行、異步執行等等。

enum DirectExecutor implements Executor {
  INSTANCE;

  @Override
  public void execute(Runnable command) {
    command.run();
  }

  @Override
  public String toString() {
    return "MoreExecutors.directExecutor()";
  }
}

 

  • 閱讀源碼收穫
  1. 大體上了解了EventBus整個的源碼實現,對訂閱者/發佈者模式有了進一步的認識。
  2. 學到了新知識點,比如CopyOnWriteArraySet數據結構。
  3. 學到了一些設計模式的用法,比如枚舉單例、委託。
  4. 初步瞭解抽象的做法。比如消息分發(抽象了dispatcher接口)。

 

 

  •  

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章