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