Guava的EventBus
一、EventBus介绍和简单实用
EventBus允许发布订阅模式中组建不用明确的注册,就可以相互进行沟通。它专为使用显式注册替换传统的 Java 进程内事件分发而设计。它不是一个通用的发布-订阅系统,也不是用于进程间通信的。
示例:
// 监听者
public class EventBusChangeRecord {
/**
* 订阅消息
* @param changeEvent
*/
@Subscribe
public void recordCustomerChange(ChangeEvent changeEvent) {
System.out.println("Listener: " + changeEvent.getData());
}
}
// 第二个监听者
public class EventListener02 {
@Subscribe
public void run(ChangeEvent changeEvent) {
System.out.println("run: "+changeEvent.getData());
}
}
// 主题
public class ChangeEvent {
private String data;
public void changeData(String data) {
this.data = data;
}
public String getData() {
return data;
}
}
public class EventBusDemo {
public static void main(String[] args) {
EventBus eventBus = new EventBus();
// 注册监听者
eventBus.register(new EventBusChangeRecord());
// 注册第二个监听者
eventBus.register(new EventListener02());
// 下面的代码必须在是最后调用 post() 方法
ChangeEvent changeEvent = new ChangeEvent();
// 改变主题的属性
changeEvent.changeData("hello, EventBus");
// 发布主题
eventBus.post(changeEvent);
// 改变主题的属性
changeEvent.changeData("hello, EventBus2");
eventBus.post(changeEvent);
}
}
-- 运行上面代码将会输出
Listener: hello, EventBus
run: hello, EventBus
Listener: hello, EventBus2
run: hello, EventBus2
二、将基于EventListener的系统修改为基于EventBus
2.1 对于监听者:Listener
(1)为了监听到特殊类型的事件(例如:CustomerChangeEvent):
- 在传统的Java事件:实现一个定义了带有事件的接口。例如CustomerChangeEventListener;
- 使用EventBus:创建一个方法它接收CustomerChangeEvent作为唯一的参数,并在上面用
@Subscribe
注解修饰;
(2) 向事件生产者注册你的监听器方法
- 在传统的Java事件:传递你的对象到每个生产者的
registerCustomerChangeEventListener
方法中。这些方法很少被定义在公共的接口中,因此为了知道每个可能的生产者,你必须知道它们的类型; - 使用EventBus:传递你的对象到EventBus的
EventBus.register(Object)
方法中。你需要确保你的对象和生产者共享一个EventBus。
(3) 监听公共事件超级类型
- 在传统的Java事件:不容易
- 使用EventBus:事件会自动分派给任何超类型的监听器。允许接口类型的监听器或Object的“通配符监听器”。
(4) 监听和探测在没有监听器情况下分派的事件
- 在传统的Java事件:添加代码到每个事件分发方法(或者使用AOP)
- 使用EventBus:订阅
DeadEvent
。EventBus将通知您任何已发布但未交付的事件。
2.2 对于生产者:Producers
(1)为了追踪事件的听众:
- 在传统的Java事件:写代码管理一个监听器的列表到你的对象中。包含同步,或者使用工具类,像
EventListenerList
- 使用EventBus:EventBus会为你做这些事情。
(2)发布一个事件给监听着
- 在传统的Java事件:写一个方法发布事件到每一个监听者,包括错误隔离和同步。
- 使用EventBus:将事件对象传递给EventBus的
EventBus.post(Object)
方法。
三、词汇表
EventBus
系统和代码使用下面的术语讨论事件分发:
事件 | 能够传递到bus到对象 |
---|---|
订阅(Subscribing) | 向EventBus注册监听器行为,以便它的处理程序接收事件。 |
监听器(Listener) | 一个希望接收事件的对象,通过暴露处理方法。 |
处理方法(Handler method) | 一个公开的方法,EventBus应该用来发布事件。处理程序方法由@Subscribe 注解标注 |
传递一个事件(Posting an event) | 通过EventBus 让事件对任何的监听器管用。 |
四、常见问题
4.1 为什么必须创建一个自己的EventBus,而不是使用一个单例的
EventBus
没有指定如何使用它。没有什么可以阻止你的应用程序为每个组件拥有单独的EventBus
实例,或者使用单独的实例来按照上下文或主题来分隔事件。这也使得在测试中设置和拆除 EventBus 对象变得很简单。
当然,如果你想要一个进程范围的 EventBus 单例,没有什么能阻止你这样做。只需让您的容器在全局范围内将 EventBus 创建为单例(或将其存储在静态字段中,如果您喜欢这种情况)。
简而言之,EventBus 不是一个单例,因为我们宁愿不为您做出那个决定。 随心所欲地使用它。
4.2 能否从一个EventBus中注销一个监听者
可以的,使用EventBus.unregister(Object)
方法,但我们发现这很少需要:
- 大多数侦听器在启动或延迟初始化时注册,并在应用程序的生命周期内持续存在。
- 范围特定的 EventBus 实例可以处理临时事件分发(例如,在请求范围的对象之间分发事件)
- 对于测试,EventBus 实例可以轻松创建和丢弃,无需显式注销。
4.3 为什么使用注解标注处理方法,而不是要求监听器实现接口?
我们认为 Event Bus 的 @Subscribe 注释与实现接口一样明确地传达了您的意图(或者可能更明确),同时让您可以自由地将事件处理程序方法放置在您希望的任何位置,并为它们提供意图揭示的名称。
传统的 Java 事件使用侦听器接口,该接口通常只支持少数几种方法——通常是一个。 这有许多缺点:
- 任何一个类都只能实现对给定事件的单个响应。
- 侦听器接口方法可能会发生冲突。
- 该方法必须以事件命名(例如,handleChangeEvent),而不是其目的(例如,recordChangeInJournal)。
- 每个事件通常都有自己的接口,没有用于一系列事件(例如所有 UI 事件)的公共父接口。