前言
Gauva 工具包是 Google 开放的一个开源工具包,就像一个瑞士军刀,小巧强大,有不少文章对其使用做个分析。平时在项目中对 Guava 的部分代码做过修改,阅读过代码,这里花一点时间,总结 Guava EventBus 的源码。文章主要包含以下内容:
- EventBus 的使用方法
- 简单的类图
- 讲解一部分源码
- EventBus 设计模式原则的分析
- 感悟
预备知识
源码截图

EventBus 模块的源码不多,是比较容易读的。
使用
public class EventBusMain {
@ToString
@AllArgsConstructor
static class MyEvent {
public int value;
}
@ToString
static class MyListener {
public int value;
@Subscribe
public void listen(MyEvent e) {
// 监听事件
System.out.println("This is " + toString() + ", 收到事件:" + e.toString());
value += e.value;
}
}
public static void main(String[] args) {
EventBus bus = new EventBus("myEventBus"); // 定义总线
MyListener myListener = new MyListener();
bus.register(myListener); // 注册监听者
System.out.println("Before: " + myListener.toString());
bus.post(new MyEvent(1)); // 向总线发布时间
bus.post(new MyEvent(2));
System.out.println("After: " + myListener.toString());
}
}
输出:
Before: EventBusMain.MyListener(value=0)
This is EventBusMain.MyListener(value=0), 收到事件:EventBusMain.MyEvent(value=1)
This is EventBusMain.MyListener(value=1), 收到事件:EventBusMain.MyEvent(value=2)
After: EventBusMain.MyListener(value=3)
如代码所示,EventBus 是一个基于发布-订阅模式构建的一个工具,其 API 非常好用易用,主要分定义和调用两部分:
- 定义事件,设计一个类
MyEvent.java
- 定义监听方法
@Subscribe public void listen(MyEvent e)
- 调用注册接口
bus.register(myListener);
- 调用发布接口
bus.post(new MyEvent(1));
发布-订阅者模式是解耦类间调用的一种最常用的方案,EventBus 就是这种方案的实现利器。
顶层类图

一个总线类会包含以下四个模块
Executor
执行器,用的是 JDK 接口,Guava 自己在工具包中也定义了自己的一组执行器,默认使用直接执行器(MoreExecutors.directExecutor())
。SubscriberExceptionHandler
,异常处理器,用于处理在执行事件过程中,处理执行异常,默认只会打印异常记录,不会把异常传递到更高上层。SubscriberRegistry
,注册器,通过反射注册监听者,重点分析。Dispatcher
,转发器,收到事件,通过注册器获得监听者并转发,重点分析。
源码分析
特点:在正式分析 EventBus 源码前,先整体说说作为一个利器工具的 EventBus 代码设计的特点。
- 1、极其漂亮的封装。从源码截图中可以发现,所有的相关类都是处于同一个 package,其目的就是通过
package权限
把大部分的实现细节都封装起来,不用客户端知道,客户端仅仅需要使用 EventBus 类和其构造方法配置一下就可以了。 - 2、单一职责特点。EventBus 这个类连上注释只有250行,核心代码100行不到,原因就是在设计过程中,把很多职责都分解开,分成四个模块,分别让不同的类自行实现。单一职责的好处还在于,每个职责本身也有自己的体系。
- 3、在及其稳定的封装和细节屏蔽环境下,继承会比组合简单。EventBus 的特点就是细节屏蔽,在这个前提下,继承表示的
is-a
关系就表现出优势了。 - 4、安全优先。工具不考虑用户是否会使用多线程,但会先保证工具的线程安全,这样,用户最多只是限制了工具的性能,而不会出现并发错误。
注册器(SubscriberRegistry)

register(Object listener)
注册监听者void unregister(Object listener)
撤销监听者(不详细写)Iterator<Subscriber> getSubscribers(Object event)
根据事件获得所有的监听者(不详细写)
register(Object listener) // 向注册器注册监听者中所有的监听方法
/**
* Registers all subscriber methods on the given listener object.
*/
void register(Object listener) {
// 拆解 listener 类,返回 事件 -> List<监听者 (Object, Method)>
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) {
// 第一次创建这个事件,则构建一个 COW List,也就是这个方法是并发安全的。
CopyOnWriteArraySet<Subscriber> newSet = new CopyOnWriteArraySet<Subscriber>();
eventSubscribers =
MoreObjects.firstNonNull(subscribers.putIfAbsent(eventType, newSet), newSet);
}
// 把拆解发现的监听者,注册到对应的 事件->List<监听者> 去
eventSubscribers.addAll(eventMethodsInListener);
}
}
// 由Object 拆解发现所有监听者的细节
/**
* Returns all subscribers for the given listener grouped by the type of event they subscribe to.
*/
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;
}
private static ImmutableList<Method> getAnnotatedMethods(Class<?> clazz) {
// 使用了LoadingCache,重复拆解同一个类时,会直接返回拆解结果
return subscriberMethodsCache.getUnchecked(clazz);
}
// 利用反射拆解的细节,获得某个类所有的方法,包括父类、接口等
private static ImmutableList<Method> getAnnotatedMethodsNotCached(Class<?> clazz) {
// Returns the set of interfaces and classes that this type is or is a subtype of. 获得这个类的所有父类及接口
Set<? extends Class<?>> supertypes = TypeToken.of(clazz).getTypes().rawTypes();
Map<MethodIdentifier, Method> identifiers = Maps.newHashMap();
// 遍历每一个类
for (Class<?> supertype : supertypes) {
// 遍历每一个方法
for (Method method : supertype.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class) && !method.isSynthetic()) {
// TODO(cgdecker): Should check for a generic parameter type and error out
Class<?>[] parameterTypes = method.getParameterTypes();
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());
}
转发器(Dispatcher)
转发器 Dispatcher 是一个抽象类,其实现类有三个,细节会在下文说
- PerThreadQueuedDispatcher
- LegacyAsyncDispatcher
- ImmediateDispatcher
转发器的核心方法
/**
* Dispatches the given {@code event} to the given {@code subscribers}.
*/
abstract void dispatch(Object event, Iterator<Subscriber> subscribers);
PerThreadQueuedDispatcher
这是 EventBus 默认的转发器,可以翻译作:"每个线程单独设置一个队列"转发器。
private static final class PerThreadQueuedDispatcher extends Dispatcher {
// This dispatcher matches the original dispatch behavior of EventBus.
/**
* Per-thread queue of events to dispatch.
* 定义一个 ThreadLocal 线程私有对象,每次获取的时候都能够获得一个队列
*/
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();
// 往队列写入需要被转发的 Event(事件本身+监听者们)
queueForThread.offer(new Event(event, subscribers));
if (!dispatching.get()) {
dispatching.set(true);
try {
Event nextEvent;
while ((nextEvent = queueForThread.poll()) != null) {
// 使用迭代器 遍历执行队列中的事件
while (nextEvent.subscribers.hasNext()) {
// 监听者通过反射执行方法: Subscribers.java:line 70
nextEvent.subscribers.next().dispatchEvent(nextEvent.event);
}
}
} finally {
// 主动释放 线程私有 对象!
dispatching.remove();
queue.remove();
}
}
}
PerThreadQueuedDispatcher 转发器,具备以下两个特点:
- 线程安全的。EventBus 是一个总线,意味着它大概率是会被不同的线程投递事件的。PerThreadQueuedDispatcher 通过 ThreadLocal 将不同线程的数据隔离开,保证线程安全。
- 这个转发器是基于
广度优先
转发的。想象一下,假如监听的事件处理中继续往总线中post事件,那就面对着深度优先
和广度优先
两种选择,这个实现是广度优先的,另外一个Dispatcher是深度优先的,等会解释。- 广度优先,意味着在转发过程中,新入的事件会被写到这个队列尾部,而不会立刻执行。
ImmediateDispatcher
与 PerThreadQueuedDispatcher 一样服务于本线程的转发器,其执行的是深度优先
执行方法。
/**
* Implementation of {@link #immediate()}.
*/
private static final class ImmediateDispatcher extends Dispatcher {
private static final ImmediateDispatcher INSTANCE = new ImmediateDispatcher();
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {
subscribers.next().dispatchEvent(event);
}
}
}
LegacyAsyncDispatcher
传统的异步分发器,这个是专门服务于多线程的。其内部会设置一个全局的队列,post 时间进去后,由多线程的 Excutor 执行器对其进行消费。使用异步就意味着,事件被监听都是无序的了,这也和我们常用的消息队列特性是一致的。
/**
* Implementation of a {@link #legacyAsync()} dispatcher.
*/
private static final class LegacyAsyncDispatcher extends Dispatcher {
/**
* Global event queue.
*/
private final ConcurrentLinkedQueue<EventWithSubscriber> queue =
Queues.newConcurrentLinkedQueue();
@Override
void dispatch(Object event, Iterator<Subscriber> subscribers) {
checkNotNull(event);
while (subscribers.hasNext()) {
// Event - 1:1 -> 监听者,作为一个事件写入队列
queue.add(new EventWithSubscriber(event, subscribers.next()));
}
EventWithSubscriber e;
while ((e = queue.poll()) != null) {
// 把事件出队,写入执行器中。
// 分开编写的目的
e.subscriber.dispatchEvent(e.event);
}
}
}
PS: 这里有个没明白的地方,为什么一定要先入队,然后再出队呢?是因为 e.subscriber 存在不一样的执行器么?
执行器(Executor)
执行器的接口是 JDK 的,意味着可以使用 JDK 以下的众多执行器去用在 EventBus,Guava 也实现了几个自己的执行器,写在 MoreExecutors.java 。这里只介绍最典型的直接执行器。
DirectExecutor
直接在提交任务的线程中执行任务,属于一种同步的执行方法,也是 EventBus 默认的执行方法。
private enum DirectExecutor implements Executor {
INSTANCE; // 通过枚举,使用 加载器 保证代码的单例
@Override
public void execute(Runnable command) {
command.run();
}
@Override
public String toString() {
return "MoreExecutors.directExecutor()";
}
}
异常处理器(SubscriberExceptionHandler)
这是处理 EventBus 在传播事件中的发生错误的处理方法。EventBus 默认对错误进行日志输出,然后屏蔽事件,防止一个监听者异常导致其他监听者失效。属于一种异常隔离的方法。
/**
* Simple logging handler for subscriber exceptions.
*/
static final class LoggingHandler implements SubscriberExceptionHandler {
static final LoggingHandler INSTANCE = new LoggingHandler();
@Override
public void handleException(Throwable exception, SubscriberExceptionContext context) {
Logger logger = logger(context);
if (logger.isLoggable(Level.SEVERE)) {
logger.log(Level.SEVERE, message(context), exception);
}
}
private static Logger logger(SubscriberExceptionContext context) {
return Logger.getLogger(EventBus.class.getName() + "." + context.getEventBus().identifier());
}
private static String message(SubscriberExceptionContext context) {
Method method = context.getSubscriberMethod();
return "Exception thrown by subscriber method "
+ method.getName()
+ '('
+ method.getParameterTypes()[0].getName()
+ ')'
+ " on subscriber "
+ context.getSubscriber()
+ " when dispatching event: "
+ context.getEvent();
}
}
监听者(Subscriber)
一个 Object
注册到注册器(SubscriberRegistry)
,会拆分生成多个 监听者
。一个Object
假如注册了n个监听Method
,则会产生多个Subscriber
。
以下是一个监听者的组成

监听者在默认的情况下是线程安全的,即使用其内部子类SynchronizedSubscriber
,也就是每次都只能有一个线程执行该监听者,只有在方法上增加注解AllowConcurrentEvents
,表示监听者方法是线程安全的。
使用:
@Subscribe
@AllowConcurrentEvents // 允许并发执行
public void listen(MyEvent e) {
System.out.println("This is " + toString() + ", 收到事件:" + e.toString());
value += e.value;
bus.post(new MyEvent(e.value + 1));
}
实现:
static final class SynchronizedSubscriber extends Subscriber {
private SynchronizedSubscriber(EventBus bus, Object target, Method method) {
super(bus, target, method);
}
@Override
void invokeSubscriberMethod(Object event) throws InvocationTargetException {
synchronized (this) {
super.invokeSubscriberMethod(event);
}
}
}
总结EventBus的工作过程
- 1、定义并编写事件类 Event
- 2、定义监听者类 Object,并以 Event 类为参数
- 3、将 Object 对象 注册到 EventBus中。
- 3.1 EventBus 首先会通过反射拆解 Object 的类型,并根据注解取出被
@Subscribe
修饰的监听方法,组成监听者(Subscriber),形成 Event -> Subscriber 的映射对 - 3.2 把 3.1 生成的监听者保存在注册器中。
- 3.1 EventBus 首先会通过反射拆解 Object 的类型,并根据注解取出被
- 4、EventBus 被提交时间 Event。
- Bus 从注册器中取出 Event 映射的所有监听者
- 通过
事件+监听者
,组成执行任务,提交给转发器
- 5、
转发器
根据自己定义的规则(深度优先、广度优先或多线程),把事件交付到监听者
处。 - 6、
监听者
通过反射执行监听方法。
EventBus的多线程模式
以上分析的都是同步版本的 EventBus,即Executor是马上执行的,即提交事件的线程会去执行所有的监听方法,执行完成才返回。
在 Guava 包中,还有一个异步的版本,提交事件后,主线程就返回了,由自行定义的 Executor 去执行监听方法。相比之下,我觉得同步版本会更难一点,因为发布-订阅模式有一种天生就容易被异步执行的感觉,故不重复记录。
设计模式思想对比
对比原则来自: 极客时间 设计模式之美 15讲-22讲 作者:王争
- 1、符合
单一职责
的设计,把模式中的各个角色都分配到不同类中; - 2、符合
扩展开放、修改关闭
的设计,客户端是无法扩展和修改的,这里的对象主要是面向需要修改 EventBus 源代码的人员,其扩展开放、修改关闭表现在: EventBus 很多不同的模块(转发器、执行器)是通过扩展去实现的。 - 3、符合
里式替换
,解释如2; - 4、符合
依赖反转
原则,即高层
的 EventBus 即 4 个组件是可以基于抽象类和接口去调用的,真正的功能和策略其实是子类在实现。; - 5、符合
KISS
,API 非常简单; - 6、符合
迪米特法则
,最小知识原则,属于高内聚、低耦合
的设计。从 EventBus 划分的 4 个组件来看,每个组件之间的调用其实是非常简单的,一般只有一两个接口; - 7、符合
DRY
功能非常确定,所以没什么重复代码; - 8、符合
接口隔离原则
,客户端调用 EventBus 使用的API非常有限且独立,这也主要得益于 Bus 的功能非常明确;
8种设计思想的对比其实有不少重叠的地方,也反证了面向对象的设计,目的还是代码重用 + 高内聚、低耦合。
后记
这是一个 Guava EventBus 的源码分析文章,前面一直在用,则是第一次总结其源码的特点。基本上可以做到完全读懂执行过程,这里在记录一下自己对这段代码的印象。
- 一个好的工具或框架,总是不会要求用户做太多事情。EventBus 的内部其实不算复杂,但提供的
API
是极其简洁的。 - 好的工具自行封装屏蔽,限制用户对其修改。为了防止误导用户的使用,EventBus 只有
EventBus
类,@Subscribe
注解,@AllowConcurrentEvents
注解 和SubscriberExceptionHandler
接口是开放给用户的,其他的组件一律通过final
和包权限
限制用户访问、修改、继承。