深入理解 Guava EventBus
Guava EventBus的原理学习
深入理解 Guava EventBus
注意: Guava 官方已不推荐使用 EventBus。如果你正在开始一个新的项目考虑使用更现代得替代方案。
正如其文档所说:“We recommend against using EventBus. It was designed many years ago, and newer libraries offer better ways to decouple components and react to events."
我们不建议使用 EventBus。它是在多年前设计的,而新的库提供了更好的组件解耦和事件响应方式。” 本文旨在深入理解其设计思想和实现原理,并探讨其替代方案。
EventBus是Guava的事件处理机制,是设计模式中的观察者模式(生产/消费者编程模型)的优雅实现。EventBus 由调度器、事件承载器、事件注册中心、事件分发器、订阅者组成。
Guava EventBus Dosc:
https://guava.dev/releases/33.5.0-jre/api/docs/com/google/common/eventbus/EventBus.html
一、什么是 EventBus?
EventBus 是 Google Guava 库中的事件总线实现,它采用发布-订阅模式,允许组件之间通过事件进行松耦合通信。与传统的观察者模式相比,EventBus 无需显式注册监听器,通过注解即可完成事件订阅。
核心优势:
•解耦组件,降低系统复杂度
• 基于注解,代码更简洁
• 支持同步和异步事件处理
• 轻量级,无需引入额外中间件
调度器
EventBus、AsyncEventBus都是一个调度的角色,区别是一个同步一个异步。
- EventBus 分发事件(Event)给listeners处理,并且提供listeners注册自己的方法。从这里我们可以看出EventBus主要是一个调度的角色。
EventBus总结
- 1.同步执行,事件发送方在发出事件之后,会等待所有的事件消费方执行完毕后,才会回来继续执行自己后面的代码。
- 2.事件发送方和事件消费方会在同一个线程中执行,消费方的执行线程取决于发送方。
- 3.同一个事件的多个订阅者,在接收到事件的顺序上面有不同。谁先注册到EventBus的,谁先执行,如果是在同一个类中的两个订阅者一起被注册到EventBus的情况,收到事件的顺序跟方法名有关。- AsyncEventBus (异步)
AsyncEventBus就是EventBus,只不过AsyncEventBus使用你指定的线程池(不指定使用默认线程池)去分发事件(Event),并且是异步进行的。
**AsyncEventBus总结**
- 1.异步执行,事件发送方异步发出事件,不会等待事件消费方是否收到,直接执行自己后面的代码。
- 2.在定义AsyncEventBus时,构造函数中会传入一个线程池。事件消费方收到异步事件时,消费方会从线程池中获取一个新的线程来执行自己的任务。
- 3.同一个事件的多个订阅者,它们的注册顺序跟接收到事件的顺序上没有任何联系,都会同时收到事件,并且都是在新的线程中,**异步并发**的执行自己的任务。事件承载器
Event事件主体,用于承载消息。
DeadEvent是一个被包装的event,只不过是一个没有订阅者无法被分发的event。我们可以在开发时注册一个DeadEvent,因为它可以检测系统事件分布中的错误配置。
事件注册中心
SubscriberRegistry是单个事件总线(EventBus)的订阅者注册表。
事件分发器
Dispatcher
主要任务是将事件分发到订阅者,并且可以不同的情况,按不同的顺序分发。
Dispatcher有三个子类,用以满足不同的分发情况
- PerThreadQueuedDispatcher (线程队列分发器)
顾名思义这里的任务会按照任务的发布顺序分发给订阅者
一个线程在处理事件过程中又发布了一个事件,PerThreadQueuedDispatcher会将后面这个事件放到最后,从而保证在单个线程上发布的所有事件都按其发布顺序分发给订阅者。注意,每个线程都要自己存储事件的队列。
PerThreadQueuedDispatcher按广度优先[1]分发事件。举个例子:
代码中发布了事件A,订阅者收到后,在执行过程中又发布了事件B和事件C,PerThreadQueuedDispatcher会确保事件A分发给所有订阅者后,再分发B、C事件。
- LegacyAsyncDispatcher (传统异步分发器)
LegacyAsyncDispatcher有一个全局队列用于存放所有事件,LegacyAsyncDispatcher特性与AsyncEventBus特性完全相符,除此之外没有其他什么特性。如果异步分发的话,最好用immediate dispatcher。
- ImmediateDispatcher(即时分发器)
在发布事件时立即将事件分发给订阅者,而不使用中间队列更改分发顺序。这实际上是深度优先[2]的调度顺序,而不是使用队列时的广度优先[1:1]。
订阅者
- Subscriber
Subscriber是特定对象(Event)的订阅方法,用于执行被分发事件。第二段说当两个订阅者在同一对象 (不是类) 上引用相同的方法时,它们是等效的,此属性用于确保不会多次注册任何订阅者方法,主要说明会对订阅者进行判重,如果是同一个对象的同一个方法,则认为是同一个订阅者,不会进行重复注册。
- SynchronizedSubscriber、
意思是说同步方法调用以确保一次只有一个线程可以执行订阅者方法(线程安全)。
二、EventBus调用原理
- listener 通过EventBus进行注册。
- SubscriberRegister 会根据listener、listener中含有【@Subscribe】注解的方法及各方法参数创建Subscriber 对象,并将其维护在Subscribers(ConcurrentMap类型,key为event类对象,value为subscriber集合)中。
- publisher发布事件Event。
- 发布Event后,EventBus会从SubscriberRegister中查找出所有订阅此事件的Subscriber,然后让Dispatcher分发Event到每一个Subscriber。

listener注册原理
在类加载的时候就会通过 getAnnotatedMethodsNotCased()获取含有@Subscribe注释的方法进行缓存到subscriberMethodsCache(LoadingCache<Class<?>, ImmutableList>, key为listener,value为method集合)。
listener注册。
类启动的时候通过显示调用将项目中的所有Listener注册到SubscribeRegisty中 eventBus.register(eventListener) 1.通过findAllSubscribers获取所有订阅者 从subscriberMethodsCache中获取订阅方法并通过Subscriber.create()创建,如果订阅者方法中标明了【@AllowConcurrentEvents】注解则创建SynchronizedSubscriber 否则创建Subscriber 2.将订阅者存到ConcurrentMap<Class<?>, CopyOnWriteArraySet<Subscriber>> subscribers 中



事件分发
publisher 发布事件Event。
EventBus 根据Event 类对象从SubscriberRegistry中获取所有订阅者。
将Event 和 eventSubscribers 交由Dispatcher去分发。
Dispatcher 将Event 分发给每个Subscribers。
Subscriber 利用反射执行订阅者方法。

EventBus 分发器原理
原理分析:
1.在代码加载时,会将所有Event缓存起来
2.当代码中调用post()方法后,通过这个Event(事件)找到对应的Subscriber(订阅者)
3.获取到Subscriber(订阅者) 分发器进行分发,执行对应事件的invoke()方法
*** 需要注意的是 : ***
- EventBus默认使用PerThreadQueuedDispatcher
在同一个线程post的Event执行顺序是有序的。用ThreadLocal<Queue> queue来实现每个线程的Event有序性,在把事件添加到queue后会有一个ThreadLocal dispatching来判断当前线程是否正在分发,如果正在分发,则这次添加的event不会马上进行分发而是等到dispatching的值为false(分发完成)才进行。
- AsyncEventBus默认使用 LegacyAsyncDispatcher
会有一个全局的队列ConcurrentLinkedQueue queue保存EventWithSubscriber(事件和subscriber),如果被不同的线程poll,不能保证在queue队列中的event是有序发布的。
- ImmediateDispatcher(即时分发器)*** 并没有地方可以显示调用***
立即分发,事件处理是嵌套执行的,一个事件没处理完引发的次级事件会优先处理
三、EventBus与Spring Event的比较
- 使用方式比较
| 项目 | 事件 | 发布者 | 发布方法 | 是否异步 | 监听者 | 注册方式 |
|---|---|---|---|---|---|---|
| EventBus | 任意对象 | EventBus | EventBus#post | 支持同步异步 | 注解Subscribe方法 | 手动注册EventBus#register |
| SpringEvent | 任意对象 | ApplicationEventPublisher | ApplicationEventPublisher#publishEvent | 支持同步异步 | 注解EventListener方法 | 系统注册 |
- 使用场景比较
| 项目 | 事件区分 | 是否支持事件簇 | 是否支持自定义event | 是否支持过滤 | 是否支持事件隔离 | 是否支持事务 | 是否支持设置订阅者消费顺序 | 复杂程度 |
|---|---|---|---|---|---|---|---|---|
| EventBus | Class | 是 | 是 | 否 | 是 | 否 | 否 | 简单 |
| Spring Event | Class | 是 | 是 | 是 | 否 | 是 | 是 | 复杂 |
- Guava EventBus 的对比总结
| 特性 | Guava EventBus | Spring Event | 优势方 |
|---|---|---|---|
| 集成度 | 第三方库,需手动管理 | Spring 框架原生支持 | Spring Event |
| 监听器注册 | 手动 register/unregister | Spring 容器自动发现 | Spring Event |
| 异步实现 | 独立的 AsyncEventBus 类 | @Async 注解 | Spring Event |
| 事务支持 | 不支持 | @TransactionalEventListener | Spring Event |
| 条件过滤 | 不支持(只能按类型) | 支持 SpEL 表达式 | Spring Event |
| 监听顺序 | 不保证 | @Order 注解 | Spring Event |
| 耦合性 | 存在“隐式耦合” | 同样存在“隐式耦合” | 平手 |
| 适用范围 | 任何 Java 项目 | 仅限 Spring 项目 | Guava EventBus |
四、总结
Guava EventBus 以其简洁优雅的设计,为我们提供了一种轻量级的解耦方案。在电商订单系统、用户行为追踪、日志收集等场景中,EventBus 能有效降低模块间的耦合度,提升代码的可维护性。但随着技术的发展,它的缺点也逐渐显现。
优点
- 解耦: EventBus 的核心价值在于它允许组件间的发布-订阅式通信,而无需组件之间显式地相互注册。这在当时是一种非常优雅的解耦方式。
- 简化事件分发: 相比于传统的 Java 事件处理机制(需要手动管理监听器列表),EventBus 通过注解
@Subscribe简化了事件订阅,使得代码更加简洁。 - 易于测试: 由于订阅者方法是普通的方法,因此可以很方便地在测试代码中直接调用它们,从而模拟 EventBus 的行为。
缺点
语法冗长: 随着 Java 8 引入 Lambda 表达式,使用 EventBus 的
@Subscribe注解和相关方法反而比直接使用 Lambda 表达式来实现监听器更繁琐。隐式的耦合: 虽然 EventBus 在代码层面实现了松耦合[3],但事件的发布者和订阅者之间仍然存在一种“隐式”的耦合[4]。这种耦合关系在编译时无法检查,只能在运行时发现,增加了代码的维护难度和不可预测性。
缺乏类型安全: 事件的订阅是基于方法签名的,如果方法签名发生变化,或者事件类型被错误地发布,编译器无法发现这些问题。
线程模型不明确: 虽然有
AsyncEventBus,但其线程管理和调度模型相对简单,对于复杂的并发场景,可能会引入难以排查的 bug。官方不再推荐: 最重要的一点是,Guava 的开发者已经明确表示不推荐使用 EventBus,并建议使用更现代的库。
替代方案
- 简单场景 → 使用 Spring Event 或原生观察者模式。
- 分布式 → 选用 Kafka 或 RabbitMQ。
广度优先 (Breadth-First, BFS): 先处理当前层级的所有事件,再处理这些事件引发的下一层事件。对应文中场景是先把事件 A 分发完,再处理 A 触发的 B、C。 ↩︎ ↩︎
深度优先 (Depth-First, DFS): 当前事件在处理过程中如果触发了新事件,会优先继续处理新事件,再回到原事件后续流程。对应文中场景是处理 A 时先进入 A 触发的子事件链。 ↩︎
松耦合 (Loose Coupling): 指系统中各个组件(或模块)之间的依赖关系很弱。一个组件的修改不会直接影响到另一个组件,它们通过一个定义良好的中介(如接口或消息)进行交互。 ↩︎
隐式耦合 (Implicit Coupling): 指两个模块之间没有直接的代码调用,但它们依赖于某个共享的、未在接口中明确声明的约定(如事件的名称或数据结构)。这种依赖关系在编译时无法检查,使得代码的真实依赖关系变得模糊。 ↩︎