背景
实际项目中有业务解耦需求,例如DDD项目中的跨领域的操作,所以进行EventBus使用调研
概念
EventBus是一个发布/订阅(Pub/Sub)事件分发模式的库,它简化了应用程序内不同组件之间的通信,允许事件的发送者和接收者解耦。EventBus的实现通常是在内存中的,因此它用于同一应用程序或同一进程内的组件间通信。
这里是一些EventBus的关键特点:
-
简单性:相比于传统的监听器模式,EventBus提供了一个更简单的API来处理事件。
-
解耦:发布者和订阅者不需要彼此了解,它们通过事件总线进行交流,减少了组件间的依赖关系。
-
易于使用:订阅者只需通过一个注解(例如Guava中的
@Subscribe
)来标记方法,即可接收相应类型的事件。 -
同步或异步:EventBus可以配置为同步或异步分发事件,允许订阅者在不同的线程中处理事件。
-
动态注册/注销:组件可以随时注册或注销自己以接收或停止接收事件。
-
事件继承:EventBus通常支持事件继承,即如果一个事件被发布,那么这个事件的所有父类型也将匹配该事件的订阅者方法。
在使用EventBus时,通常的步骤包括:
- 定义事件:创建事件类,作为通信的载体。
- 创建订阅者:定义订阅者并使用注解标记一个或多个方法,以便接收事件。
- 注册订阅者:在EventBus上注册订阅者使其开始接收事件。
- 发布事件:通过EventBus发布事件,所有注册了该事件的订阅者都会收到通知并执行对应的事件处理方法。
- 注销订阅者:当不再需要接收事件时,可以从EventBus上注销订阅者。
EventBus的一个流行实现是Google的Guava库中的EventBus。它广泛用于处理Java应用程序中的组件间通信。
总的来说,EventBus是一种便捷的设计模式的实现,它通过提供一个中央机制来分发事件,帮助应用程序组件保持清晰和灵活的组织结构。
用例
下面是一个简单的例子,展示了如何在Java中使用Guava Event Bus和@Subscribe
注解:
首先,添加Guava库的依赖。如果你使用的是Maven,可以在pom.xml
文件中添加以下依赖:
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version> <!-- Use the latest version available -->
</dependency>
然后,创建订阅者和发布者:
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
// 定义事件
class CustomEvent {
private final String message;
CustomEvent(String message) {
this.message = message;
}
String getMessage() {
return message;
}
}
// 订阅者
class EventListener {
@Subscribe
public void onEvent(CustomEvent event) {
System.out.println("Message received: " + event.getMessage());
}
}
public class EventBusExample {
public static void main(String[] args) {
EventBus eventBus = new EventBus();
EventListener listener = new EventListener();
// 注册订阅者
eventBus.register(listener);
// 发布事件
eventBus.post(new CustomEvent("Hello World!"));
}
}
在这个例子中,EventListener
类中的 onEvent
方法被 @Subscribe
注解标记,表明它是一个事件处理器。当你向 EventBus
发布一个 CustomEvent
事件时,EventBus
会自动调用所有注册了该类型事件的处理器方法。
@Subscribe
注解的作用是将方法注册为事件订阅者,这样当事件发布到 EventBus
时,就会自动调用这些方法。这是观察者设计模式的一个实现,允许实现组件间的低耦合通信。
Event Bus 的实现原理涉及以下关键概念:
-
事件存储:
- 一个中央存储,用于维护事件类型与对应订阅者处理方法的映射关系。这通常是通过哈希表或者多重映射实现的。
-
订阅者注册:
- 订阅者通过调用
register
方法注册自己,Event Bus 遍历订阅者的所有方法,找到带有@Subscribe
注解的方法,并将它们与事件类型关联起来存储在中央存储中。
- 订阅者通过调用
-
事件发布:
- 当一个事件被发布时,Event Bus 查找中央存储中所有注册的和该事件类型匹配的订阅者和处理方法。
-
事件分发:
- Event Bus 遍历找到的订阅者和处理方法,使用反射机制调用订阅者的处理方法,并将事件对象作为参数传递。
-
线程模型:
- Event Bus 可以支持不同的线程模型,例如直接在发布事件的线程中处理、在特定的后台线程中处理,或者利用线程池进行异步处理。
-
异常处理:
- Event Bus 在分发事件时也会有相应的异常处理机制,确保一个订阅者的失败不会影响其他订阅者。
-
取消注册:
- 订阅者可以调用
unregister
方法从 Event Bus 中取消注册,之后它将不再接收事件通知。
- 订阅者可以调用
-
事件继承:
- Event Bus 通常支持事件继承,这意味着如果一个事件继承自另一个事件类,那么订阅了父类事件的订阅者也会接收到子类的事件。
手写一个EventBus
举个简化的例子,以下是一个简单的 Event Bus 的实现伪代码:
public class EventBus {
private final ConcurrentHashMap<Class<?>, List<SubscriberMethod>> subscribersMap = new ConcurrentHashMap<>();
public void register(Object subscriber) {
// 获取subscriber类中所有带有@Subscribe注解的方法
List<Method> subscriberMethods = getAnnotatedMethods(subscriber.getClass());
for (Method method : subscriberMethods) {
Class<?> eventType = method.getParameterTypes()[0];
subscribersMap.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
.add(new SubscriberMethod(subscriber, method));
}
}
public void post(Object event) {
Class<?> eventType = event.getClass();
List<SubscriberMethod> subscriberMethods = subscribersMap.get(eventType);
if (subscriberMethods != null) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
// 反射调用方法
Method method = subscriberMethod.getMethod();
try {
method.invoke(subscriberMethod.getSubscriber(), event);
} catch (IllegalAccessException | InvocationTargetException e) {
// 处理异常
}
}
}
}
public void unregister(Object subscriber) {
// 移除subscriber的所有订阅方法
subscribersMap.values().forEach(subscribers -> subscribers.removeIf(subscriberMethod ->
subscriberMethod.getSubscriber().equals(subscriber)));
}
// 用于存储 subscriber 和 method 的关系
private static class SubscriberMethod {
private final Object subscriber;
private final Method method;
SubscriberMethod(Object subscriber, Method method) {
this.subscriber = subscriber;
this.method = method;
}
// getters...
}
// 获取带有@Subscribe注解的方法
private List<Method> getAnnotatedMethods(Class<?> type) {
//...
}
}
在这个例子中,我们使用了 ConcurrentHashMap
来存储事件类型到订阅者方法的映射。每次 post
被调用时,Event Bus 都会查找映射并通过反射调用订阅者的方法。
实际上,真实的 Event Bus 实现(如Guava的EventBus)会更复杂,包括对线程安全、异常处理、事件继承以及性能优化等方面的考虑。
EventBus可以通过使用不同的Executor
来配置为异步模式。在Guava的EventBus中,你可以在创建EventBus实例时传入一个Executor
,该Executor
定义了事件处理的并发策略。
以下是如何配置Guava EventBus以异步方式分发事件的示例:
import com.google.common.eventbus.AsyncEventBus;
import java.util.concurrent.Executors;
// 创建一个Executor
Executor executor = Executors.newCachedThreadPool();
// 使用Executor创建一个AsyncEventBus
AsyncEventBus eventBus = new AsyncEventBus(executor);
// 其余的注册和发布操作与普通的EventBus相同
在这个例子中,Executors.newCachedThreadPool()
创建了一个缓存线程池,这意味着如果线程池中有空闲的线程,它会重用它们;如果没有,它会创建新的线程。当不再需要线程时,它们会被回收。
使用AsyncEventBus
而不是普通的EventBus
,可以确保所有通过post
提交的事件都将在提供的Executor
指定的线程中异步处理,从而不会阻塞发布事件的线程。
请注意,异步模式下,事件处理的顺序不能保证,且事件处理器(订阅者方法)需要是线程安全的,因为它们可能会被并发调用。