`com.google.common.eventbus` 是 Google Guava 库中提供的事件总线框架,它实现了发布-订阅模式

本文介绍了Google Guava库中的EventBus,它支持事件驱动的组件间通信,通过发布-订阅模型简化代码,提高可维护性。我们探讨了如何创建事件、注册订阅者,以及如何利用优先级、异步处理和泛化等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以下分析基于 Guava 28 以后版本的源码与社区实践,对 com.google.common.eventbus 做一个系统性拆解。你可以把它当成一份“导航图”,需要深入某一块时再按图索骥。


  1. 角色与职责一览

类 / 接口定位一句话说明
EventBus同步总线“傻瓜式”观察者模式实现,默认在当前线程分发
AsyncEventBus异步总线只是把 EventBus 的 Executor 换成线程池
Subscribe注解打在只有一个参数的成员方法上,声明“我关心这个事件”
AllowConcurrentEvents注解打在 Subscribe 方法上,告诉框架“我是线程安全的,可并发执行”
Subscriber内部包装一个 Subscribe 方法 + 目标对象 的一层薄封装,负责真正反射调用
SubscriberRegistry注册表事件类型 ⇒ CopyOnWriteArraySet 的多值 Map,解决“一对多”分发
Dispatcher策略抽象分发算法被抽出来,内置 3 种实现(见下)
DeadEvent兜底的“事件”如果 post 的事件没有任何订阅者,就发送一个 DeadEvent 作为补偿
SubscriberExceptionHandler / SubscriberExceptionContext异常处理统一处理订阅方法抛出的任何 Throwable

  1. 事件分发三驾马车(Dispatcher)

同步/异步的差异只是 Dispatcher 的不同实现:

  1. ImmediateDispatcher
    最简单:for-loop 直接在当前线程调用 Subscriber → 同步阻塞。

  2. PerThreadQueuedDispatcher(EventBus 默认)
    每个线程维护一个 ThreadLocal<List>,先把事件排进队列,再在当前线程依次派发,避免同一线程递归 post 时出现栈溢出。

  3. LegacyAsyncDispatcher(AsyncEventBus 专用)
    内部维护一个 ConcurrentLinkedQueue<EventWithSubscriber>,先一次性把所有 Subscriber 封装进队列,再由 Executor 并发消费。
    注意:如果线程池饱和可能 OOM,这也是早期版本踩坑最多的点。


  1. 注册与查找流程

register(Object listener) → SubscriberRegistry:

  1. 通过反射遍历 listener 类及其父类所有方法 → 找到带 Subscribe 注解的方法。
    结果缓存在 LoadingCache<Class<?>, ImmutableList<Method>> 中,避免重复反射。
  2. 把“事件类型 → Subscriber”塞进 ConcurrentHashMap<Class<?>, CopyOnWriteArraySet<Subscriber>>
    用 CopyOnWriteArraySet 保证读多写少且线程安全。

post(Object event) 时:

  1. flattenHierarchy(event.getClass()) 拿到事件类型的全部父类/接口,保证“订阅 Fruit 也能收到 Apple”的多态效果。
  2. 从 Map 里拿到所有 Subscriber,交给 Dispatcher 去 invoke。

  1. 线程安全与并发细节

  • Subscribe 方法默认是同步串行:框架为每个事件-订阅者组合创建 Runnable,提交给 EventBus 持有的 Executor。
    – EventBus 默认 Executor = MoreExecutors.directExecutor() → 当前线程执行。
    – AsyncEventBus 把 Executor 换成业务线程池,实现真正的异步。

  • @AllowConcurrentEvents 的作用
    – 如果加在 Subscribe 方法上,Subscriber 不会包一层 synchronized(this),允许多线程并发调用。
    – 不加则认为实现者不是线程安全的,框架会用 SynchronizedSubscriber 串行化调用。

  • 异常不会跨线程冒泡
    Subscriber 抛出的任何 Throwable 最终会被 SubscriberExceptionHandler 捕获,默认行为是打印堆栈;你可以通过 EventBus 构造函数注入自己的处理器做报警或降级。


  1. 与 Spring、业务系统集成的最佳实践

  • Spring 场景
    – 写一个 @Component 作为事件中心,内部持有 AsyncEventBus(注入业务线程池),在 @PostConstruct 里扫所有 @EventBusListener Bean 并 register。
    – 业务方只需写 @Subscribe 方法即可,实现彻底解耦。

  • 线程池选择
    – 高并发场景避免使用无界队列;给 AsyncEventBus 的 Executor 配一个有队列长度限制的线程池,防止事件爆炸导致 OOM。

  • 监控
    – 包装线程池采集队列长度、任务耗时。
    – 在自定义 SubscriberExceptionHandler 里打点,监控业务异常率。


  1. 一张图总结调用链

post(event)
→ SubscriberRegistry.getSubscribers(eventType)
→ Dispatcher.dispatch(event, Iterator)
→ Subscriber.dispatchEvent(event)
→ executor.execute(() → Subscriber.invokeSubscriberMethod(event))
→ 反射调用你的 @Subscribe 方法


  1. 小结与建议

  • 小系统 / 单机 → EventBus 足够简单,省掉消息中间件。
  • 需要持久化、堆积、分布式 → 直接上 Kafka/RabbitMQ,而不是把 AsyncEventBus 当万能药。
  • 升级提示
    – Guava 30 以后官方把 EventBus 标记为 @Beta,API 仍可能调整;追求稳定的团队可以迁移到 Spring ApplicationEvent 或 Reactor。

在这里插入图片描述

com.google.common.eventbus 是 Google Guava 库中提供的事件总线框架,它实现了发布-订阅模式,用于简化组件之间的通信,减少组件间的直接依赖。

核心概念

  1. EventBus:事件总线的核心类,负责管理事件的发布和订阅关系
  2. 事件(Event):任意的 Java 对象,作为消息载体在组件间传递
  3. 订阅者(Subscriber):注册到 EventBus 上,监听特定类型事件的对象
  4. 发布者(Publisher):通过 EventBus 发布事件的对象

核心类与方法

  1. EventBus 类

    • 构造方法:EventBus()EventBus(String identifier)
    • 注册订阅者:void register(Object subscriber)
    • 取消注册:void unregister(Object subscriber)
    • 发布事件:void post(Object event)
  2. 订阅方法
    订阅者通过标注 @Subscribe 来声明事件处理方法:

    @Subscribe
    public void handleEvent(SomeEvent event) {
        // 处理事件
    }
    

    方法参数类型决定了它要订阅的事件类型

  3. AsyncEventBus
    继承自 EventBus,用于异步处理事件,构造时需要传入 Executor:

    EventBus asyncBus = new AsyncEventBus(Executors.newCachedThreadPool());
    

工作流程

  1. 创建 EventBus 实例
  2. 将订阅者对象注册到 EventBus
  3. 发布者通过 EventBus 发布事件
  4. EventBus 自动将事件分发给所有订阅了该类型事件的订阅者
  5. 不需要时取消订阅者注册

示例代码

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

// 定义事件
class MessageEvent {
    private String message;
    
    public MessageEvent(String message) {
        this.message = message;
    }
    
    public String getMessage() {
        return message;
    }
}

// 定义订阅者
class MessageSubscriber {
    @Subscribe
    public void onMessage(MessageEvent event) {
        System.out.println("Received message: " + event.getMessage());
    }
}

// 使用示例
public class EventBusDemo {
    public static void main(String[] args) {
        // 创建事件总线
        EventBus eventBus = new EventBus();
        
        // 创建并注册订阅者
        MessageSubscriber subscriber = new MessageSubscriber();
        eventBus.register(subscriber);
        
        // 发布事件
        eventBus.post(new MessageEvent("Hello, EventBus!"));
        
        // 取消注册
        eventBus.unregister(subscriber);
    }
}

特点与优势

  1. 解耦:组件间通过事件通信,无需知道彼此的存在
  2. 简化代码:减少了大量的接口和回调代码
  3. 灵活性:支持同步和异步两种模式
  4. 层次化事件:可以订阅父类事件来接收所有子类事件

注意事项

  1. 订阅方法必须是 public 的
  2. 事件处理默认是同步的,使用 AsyncEventBus 实现异步
  3. 异常处理:同步模式下,事件处理抛出的异常会向上传播;异步模式下需要特殊处理
  4. 线程安全:EventBus 本身是线程安全的,但订阅者方法需要考虑线程安全问题

通过使用 Guava EventBus,可以使代码结构更清晰,组件间耦合度更低,尤其适合在复杂应用中实现模块间的通信。

com.google.common.eventbus 是 Google Guava 库中的一个包,它提供了一个简单的发布-订阅事件总线,用于在应用程序中实现事件驱动的通信。这个事件总线允许你创建事件和订阅者,并且能够将事件广播到所有注册的订阅者。
使用 EventBus,你可以解耦组件之间的依赖,让它们仅通过发布和订阅事件进行通信,而不需要知道彼此的存在。这有助于简化代码,提高可维护性,并使组件更容易测试。
在 Guava 库中,com.google.common.eventbus 包提供了 EventBus 类,它是核心的类,用于创建事件和注册订阅者。你可以使用 EventBus 类来创建事件对象,并使用 post() 方法将事件发布到总线。然后,你可以使用 register() 方法将一个对象注册为订阅者,以便接收发布到总线的事件。
这个包还提供了其他一些有用的类和工具,如 AsyncEventBus(一个异步的事件总线),以及异常处理和日志记录工具等。
使用 com.google.common.eventbus 包可以帮助你轻松地实现事件驱动的通信,特别是在处理大量事件或需要解耦组件的情况下。为了更好地理解如何使用 com.google.common.eventbus,让我们看一个简单的示例:

  1. 首先,你需要将 Guava 库添加到项目的依赖中。如果你使用 Maven,可以在 pom.xml 文件中添加以下依赖:
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>30.1-jre</version> <!-- 使用你需要的版本 -->
</dependency>
  1. 创建一个事件类。事件类通常定义在事件总线之外,并且不需要实现任何特殊接口:
public class ExampleEvent {
    private final String message;
    public ExampleEvent(String message) {
        this.message = message;
    }
    public String getMessage() {
        return message;
    }
}
  1. 创建一个订阅者类,该类将处理事件:
import com.google.common.eventbus.Subscribe;
public class ExampleSubscriber {
    public void handleEvent(ExampleEvent event) {
        System.out.println("Received event: " + event.getMessage());
    }
}
  1. 在主类中,创建事件总线和注册订阅者:
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.SubscriberExceptionContext;
import com.google.common.eventbus.SubscriberExceptionHandler;
public class EventBusExample {
    public static void main(String[] args) {
        // 创建事件总线并设置异常处理程序
        EventBus eventBus = new EventBus(new SubscriberExceptionHandler() {
            @Override
            public void handleException(Throwable exception, SubscriberExceptionContext context) {
                System.err.println("Error delivering event: " + context.getEvent());
            }
        });
        // 创建订阅者并注册到事件总线
        ExampleSubscriber subscriber = new ExampleSubscriber();
        eventBus.register(subscriber);
        // 发布事件到总线
        eventBus.post(new ExampleEvent("Hello, EventBus!"));
    }
}

运行 EventBusExample 类,你应该会看到以下输出:

Received event: Hello, EventBus!
```当然可以。让我们进一步探索 `com.google.common.eventbus` 的其他特性和用例。
5. **解耦与灵活的设计**:使用事件总线,你可以将不同部分的应用程序代码解耦。一个部分的代码可以发布事件,而另一个部分的代码可以订阅并响应这些事件,而无需知道彼此的具体实现。这使得代码更加模块化,更易于测试和维护。
6. **事件的优先级**:Guava 事件总线允许你为订阅者设置优先级。这意味着,当多个订阅者都订阅同一个事件时,优先级高的订阅者将首先收到事件。这对于需要快速响应或高优先级的需求非常有用。
7. **异步处理**:默认情况下,事件总线使用同步方法来分发事件。但是,如果你需要异步处理,你可以在注册订阅者时传递一个 `Executor`,这样就可以在指定的线程上异步地调用订阅者的方法。
8. **事件的泛化**:如果你有一个父类型和多个子类型,并且你想让一个订阅者能够同时处理这些类型的事件,你可以使用 `GenericEvent`。这样,你可以将任何类型的事件传递给订阅者,并在订阅者的方法中使用类型检查和特定于类型的逻辑。
9. **取消事件的分发**:在某些情况下,你可能希望在事件分发到所有订阅者之前取消事件的分发。这可以通过调用 `EventBus.prototype.post()` 的返回值来实现。如果返回 `true`,则表示事件已被取消;如果返回 `false`,则表示事件已被分发。
总之,`com.google.common.eventbus` 是一个强大且灵活的工具,可以帮助你更好地组织和管理你的代码。通过合理地使用事件总线,你可以创建更加模块化、可维护和可扩展的代码库。
The EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another (and thus be aware of each other).

See:
          Description

Class Summary
AsyncEventBus 	An EventBus that takes the Executor of your choice and uses it to dispatch events, allowing dispatch to occur asynchronously.
DeadEvent 	Wraps an event that was posted, but which had no subscribers and thus could not be delivered.
EventBus 	Dispatches events to listeners, and provides ways for listeners to register themselves.
 

Annotation Types Summary
AllowConcurrentEvents 	Marks an event handling method as being thread-safe.
Subscribe 	Marks a method as an event handler, as used by AnnotatedHandlerFinder and EventBus.
 

Package com.google.common.eventbus Description

The EventBus allows publish-subscribe-style communication between components without requiring the components to explicitly register with one another (and thus be aware of each other). It is designed exclusively to replace traditional Java in-process event distribution using explicit registration. It is not a general-purpose publish-subscribe system, nor is it intended for interprocess communication.
One-Minute Guide
Converting an existing EventListener-based system to use the EventBus is easy.
For Listeners
To listen for a specific flavor of event (say, a CustomerChangeEvent)...

    ...in traditional Java events: implement an interface defined with the event — such as CustomerChangeEventListener.
    ...with EventBus: create a method that accepts CustomerChangeEvent as its sole argument, and mark it with the Subscribe annotation.

To register your listener methods with the event producers...

    ...in traditional Java events: pass your object to each producer's registerCustomerChangeEventListener method. These methods are rarely defined in common interfaces, so in addition to knowing every possible producer, you must also know its type.
    ...with EventBus: pass your object to the EventBus.register(Object) method on an EventBus. You'll need to make sure that your object shares an EventBus instance with the event producers.

To listen for a common event supertype (such as EventObject or Object)...

    ...in traditional Java events: not easy.
    ...with EventBus: events are automatically dispatched to listeners of any supertype, allowing listeners for interface types or "wildcard listeners" for Object.

To listen for and detect events that were dispatched without listeners...

    ...in traditional Java events: add code to each event-dispatching method (perhaps using AOP).
    ...with EventBus: subscribe to DeadEvent. The EventBus will notify you of any events that were posted but not delivered. (Handy for debugging.)

For Producers
To keep track of listeners to your events...

    ...in traditional Java events: write code to manage a list of listeners to your object, including synchronization, or use a utility class like EventListenerList.
    ...with EventBus: EventBus does this for you.

To dispatch an event to listeners...

    ...in traditional Java events: write a method to dispatch events to each event listener, including error isolation and (if desired) asynchronicity.
    ...with EventBus: pass the event object to an EventBus's EventBus.post(Object) method.

Glossary
The EventBus system and code use the following terms to discuss event distribution:

Event
    Any object that may be posted to a bus.
Subscribing
    The act of registering a listener with an EventBus, so that its handler methods will receive events.
Listener
    An object that wishes to receive events, by exposing handler methods. 
Handler method
    A public method that the EventBus should use to deliver posted events. Handler methods are marked by the Subscribe annotation.
Posting an event
    Making the event available to any listeners through the EventBus. 

FAQ
Why must I create my own Event Bus, rather than using a singleton?
The Event Bus doesn't specify how you use it; there's nothing stopping your application from having separate EventBus instances for each component, or using separate instances to separate events by context or topic. This also makes it trivial to set up and tear down EventBus objects in your tests.

Of course, if you'd like to have a process-wide EventBus singleton, there's nothing stopping you from doing it that way. Simply have your container (such as Guice) create the EventBus as a singleton at global scope (or stash it in a static field, if you're into that sort of thing).

In short, the EventBus is not a singleton because we'd rather not make that decision for you. Use it how you like.
Can I unregister a listener from the Event Bus?
Currently, no -- a listener registered with an EventBus instance will continue to receive events until the EventBus itself is disposed.

In the apps using EventBus so far, this has not been a problem:

    Most listeners are registered on startup or lazy initialization, and persist for the life of the application.
    Scope-specific EventBus instances can handle temporary event distribution (e.g. distributing events among request-scoped objects)
    For testing, EventBus instances can be easily created and thrown away, removing the need for explicit unregistration. 

Why use an annotation to mark handler methods, rather than requiring the listener to implement an interface?
We feel that the Event Bus's @Subscribe annotation conveys your intentions just as explicitly as implementing an interface (or perhaps more so), while leaving you free to place event handler methods wherever you wish and give them intention-revealing names.

Traditional Java Events use a listener interface which typically sports only a handful of methods -- typically one. This has a number of disadvantages:

    Any one class can only implement a single response to a given event.
    Listener interface methods may conflict.
    The method must be named after the event (e.g. handleChangeEvent), rather than its purpose (e.g. recordChangeInJournal).
    Each event usually has its own interface, without a common parent interface for a family of events (e.g. all UI events). 

The difficulties in implementing this cleanly has given rise to a pattern, particularly common in Swing apps, of using tiny anonymous classes to implement event listener interfaces.

Compare these two cases:

   class ChangeRecorder {
     void setCustomer(Customer cust) {
       cust.addChangeListener(new ChangeListener() {
         void customerChanged(ChangeEvent e) {
           recordChange(e.getChange());
         }
       };
     }
   }

   // Class is typically registered by the container.
   class EventBusChangeRecorder {
     @Subscribe void recordCustomerChange(ChangeEvent e) {
       recordChange(e.getChange());
     }
   }

The intent is actually clearer in the second case: there's less noise code, and the event handler has a clear and meaningful name.
What about a generic Handler<T> interface?
Some have proposed a generic Handler<T> interface for EventBus listeners. This runs into issues with Java's use of type erasure, not to mention problems in usability.

Let's say the interface looked something like the following:

   interface Handler<T> {
     void handleEvent(T event);
   }

Due to erasure, no single class can implement a generic interface more than once with different type parameters. This is a giant step backwards from traditional Java Events, where even if actionPerformed and keyPressed aren't very meaningful names, at least you can implement both methods!
Doesn't EventBus destroy static typing and eliminate automated refactoring support?
Some have freaked out about EventBus's register(Object) and post(Object) methods' use of the Object type.

Object is used here for a good reason: the Event Bus library places no restrictions on the types of either your event listeners (as in register(Object)) or the events themselves (in post(Object)).

Event handler methods, on the other hand, must explicitly declare their argument type -- the type of event desired (or one of its supertypes). Thus, searching for references to an event class will instantly find all handler methods for that event, and renaming the type will affect all handler methods within view of your IDE (and any code that creates the event).

It's true that you can rename your @Subscribed event handler methods at will; Event Bus will not stop this or do anything to propagate the rename because, to Event Bus, the names of your handler methods are irrelevant. Test code that calls the methods directly, of course, will be affected by your renaming -- but that's what your refactoring tools are for.
What happens if I register a listener without any handler methods?
Nothing at all.

The Event Bus was designed to integrate with containers and module systems, with Guice as the prototypical example. In these cases, it's convenient to have the container/factory/environment pass every created object to an EventBus's register(Object) method.

This way, any object created by the container/factory/environment can hook into the system's event model simply by exposing handler methods.
What Event Bus problems can be detected at compile time?
Any problem that can be unambiguously detected by Java's type system. For example, defining a handler method for a nonexistent event type.
What Event Bus problems can be detected immediately at registration?
Immediately upon invoking register(Object) , the listener being registered is checked for the well-formedness of its handler methods. Specifically, any methods marked with @Subscribe must take only a single argument.

Any violations of this rule will cause an IllegalArgumentException to be thrown.

(This check could be moved to compile-time using APT, a solution we're researching.)
What Event Bus problems may only be detected later, at runtime?
If a component posts events with no registered listeners, it may indicate an error (typically an indication that you missed a @Subscribe annotation, or that the listening component is not loaded).

(Note that this is not necessarily indicative of a problem. There are many cases where an application will deliberately ignore a posted event, particularly if the event is coming from code you don't control.)

To handle such events, register a handler method for the DeadEvent class. Whenever EventBus receives an event with no registered handlers, it will turn it into a DeadEvent and pass it your way -- allowing you to log it or otherwise recover.
How do I test event listeners and their handler methods?
Because handler methods on your listener classes are normal methods, you can simply call them from your test code to simulate the EventBus. 
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/6901219abcc14209b019c52285e795b5.jpeg#pic_center)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Bol5261

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值