Google Guava EventBus — 概念、原理、场景与代码示例

Google Guava EventBus --- 概念、原理、场景与代码示例

一、什么是 Guava EventBus

Guava EventBus 是 Google Guava 库提供的一个轻量级进程内事件总线。它让同一个 JVM 内的组件可以通过发布/订阅事件进行通信,不需要显式注册回调接口或直接持有对方的引用。

一句话总结

复制代码
EventBus = 一个内存中的"公告板"
发布者在公告板上贴通知,所有注册了的订阅者自动看到并处理

Guava 是什么

Google Guava 是 Google 开源的 Java 核心工具库,包含集合、缓存、并发、字符串处理、IO 等常用工具。EventBus 是其中的一个模块。

xml 复制代码
<!-- Maven 依赖 -->
<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>32.1.3-jre</version>
</dependency>

注:

博客:

https://blog.csdn.net/badao_liumang_qizhi

二、核心概念

概念 说明
EventBus 事件总线实例,负责分发事件
@Subscribe 标注在方法上,表示该方法是事件处理器
post(event) 发布事件
register(subscriber) 注册订阅者
unregister(subscriber) 取消注册
DeadEvent 没有任何订阅者处理的事件会包装成 DeadEvent

工作流程

复制代码
1. 创建 EventBus 实例
2. 订阅者编写带 @Subscribe 注解的方法
3. 将订阅者注册到 EventBus
4. 发布者调用 eventBus.post(event)
5. EventBus 找到所有匹配的 @Subscribe 方法并调用

三、基础用法

最简示例

java 复制代码
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;

// 1. 定义事件(普通 POJO)
public class OrderCreatedEvent {
    private final String orderId;
    private final String userName;

    public OrderCreatedEvent(String orderId, String userName) {
        this.orderId = orderId;
        this.userName = userName;
    }
    public String getOrderId() { return orderId; }
    public String getUserName() { return userName; }
}

// 2. 定义订阅者
public class SmsSubscriber {
    @Subscribe
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("发短信:" + event.getUserName() + ",您的订单" 
            + event.getOrderId() + "已创建");
    }
}

public class PointsSubscriber {
    @Subscribe
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("加积分:用户" + event.getUserName() + "加100分");
    }
}

// 3. 使用
public class Main {
    public static void main(String[] args) {
        // 创建事件总线
        EventBus eventBus = new EventBus("order-bus");

        // 注册订阅者
        eventBus.register(new SmsSubscriber());
        eventBus.register(new PointsSubscriber());

        // 发布事件
        eventBus.post(new OrderCreatedEvent("SO001", "张三"));
    }
}

// 输出:
// 发短信:张三,您的订单SO001已创建
// 加积分:用户张三加100分

四、同步 vs 异步

同步 EventBus(默认)

java 复制代码
// 同步:post() 会等所有订阅者执行完才返回
EventBus eventBus = new EventBus("sync-bus");

eventBus.register(new Object() {
    @Subscribe
    public void handle(String msg) {
        // 假设这里耗时3秒
        Thread.sleep(3000);
        System.out.println("处理完成:" + msg);
    }
});

System.out.println("发布前");
eventBus.post("hello");  // 会阻塞3秒
System.out.println("发布后");  // 3秒后才打印

// 输出顺序:
// 发布前
// 处理完成:hello     ← 等了3秒
// 发布后

异步 AsyncEventBus

java 复制代码
import com.google.common.eventbus.AsyncEventBus;
import java.util.concurrent.Executors;

// 异步:post() 立即返回,订阅者在线程池中执行
AsyncEventBus asyncBus = new AsyncEventBus("async-bus", 
    Executors.newFixedThreadPool(4));

asyncBus.register(new Object() {
    @Subscribe
    public void handle(String msg) {
        Thread.sleep(3000);
        System.out.println(Thread.currentThread().getName() + " 处理完成:" + msg);
    }
});

System.out.println("发布前");
asyncBus.post("hello");  // 立即返回
System.out.println("发布后");  // 立刻打印

// 输出顺序:
// 发布前
// 发布后              ← 立即返回
// pool-1-thread-1 处理完成:hello    ← 3秒后在异步线程中完成

五、事件继承

Guava EventBus 支持事件类型的继承匹配------订阅父类事件的处理器也会收到子类事件:

java 复制代码
// 事件继承体系
public class BaseEvent {
    private final String source;
    public BaseEvent(String source) { this.source = source; }
    public String getSource() { return source; }
}

public class OrderEvent extends BaseEvent {
    private final String orderId;
    public OrderEvent(String source, String orderId) {
        super(source);
        this.orderId = orderId;
    }
    public String getOrderId() { return orderId; }
}

public class PaymentEvent extends BaseEvent {
    private final BigDecimal amount;
    public PaymentEvent(String source, BigDecimal amount) {
        super(source);
        this.amount = amount;
    }
    public BigDecimal getAmount() { return amount; }
}

// 订阅者
public class AuditSubscriber {
    // 订阅BaseEvent → 所有子类事件都能收到
    @Subscribe
    public void onAnyEvent(BaseEvent event) {
        System.out.println("审计日志:收到事件来自 " + event.getSource());
    }
}

public class OrderSubscriber {
    // 只订阅OrderEvent
    @Subscribe
    public void onOrder(OrderEvent event) {
        System.out.println("处理订单:" + event.getOrderId());
    }
}

// 使用
EventBus bus = new EventBus();
bus.register(new AuditSubscriber());
bus.register(new OrderSubscriber());

bus.post(new OrderEvent("web", "SO001"));
// 输出:
// 审计日志:收到事件来自 web       ← AuditSubscriber 收到(因为OrderEvent继承BaseEvent)
// 处理订单:SO001                 ← OrderSubscriber 收到

bus.post(new PaymentEvent("app", new BigDecimal("99.9")));
// 输出:
// 审计日志:收到事件来自 app       ← AuditSubscriber 收到
//                                 ← OrderSubscriber 不收到(PaymentEvent ≠ OrderEvent)

六、DeadEvent --- 无人处理的事件

当一个事件发布后没有任何 @Subscribe 方法能处理它时,EventBus 会将其包装为 DeadEvent

java 复制代码
import com.google.common.eventbus.DeadEvent;

public class DeadEventHandler {
    @Subscribe
    public void onDeadEvent(DeadEvent deadEvent) {
        System.out.println("警告:没有处理器的事件 → " + deadEvent.getEvent());
        System.out.println("来源EventBus:" + deadEvent.getSource());
    }
}

EventBus bus = new EventBus();
bus.register(new DeadEventHandler());

// 发布一个没人订阅的事件
bus.post(new SomeUnhandledEvent());
// 输出:警告:没有处理器的事件 → SomeUnhandledEvent@xxxx

这对调试很有用------可以发现被遗忘的事件或拼写错误的订阅方法。


七、异常处理

默认行为

同步 EventBus 中,如果一个订阅者抛出异常,默认会打印日志但继续分发给其他订阅者。

自定义异常处理器

java 复制代码
EventBus bus = new EventBus(new SubscriberExceptionHandler() {
    @Override
    public void handleException(Throwable exception, SubscriberExceptionContext context) {
        System.err.println("订阅者异常!");
        System.err.println("事件:" + context.getEvent());
        System.err.println("订阅者:" + context.getSubscriber());
        System.err.println("方法:" + context.getSubscriberMethod());
        exception.printStackTrace();
        // 可以在这里发告警、记日志等
    }
});

bus.register(new Object() {
    @Subscribe
    public void handle(String msg) {
        throw new RuntimeException("处理失败!");
    }
});

bus.register(new Object() {
    @Subscribe
    public void handle(String msg) {
        System.out.println("我还是能收到:" + msg);  // 不受上面异常影响
    }
});

bus.post("test");
// 输出:
// 订阅者异常!
// 事件:test
// ...
// 我还是能收到:test    ← 其他订阅者正常执行

八、在 Spring 项目中集成

配置

java 复制代码
@Configuration
public class EventBusConfig {

    @Bean
    public EventBus syncEventBus() {
        return new EventBus("sync-bus");
    }

    @Bean
    public AsyncEventBus asyncEventBus() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setQueueCapacity(100);
        executor.setThreadNamePrefix("event-");
        executor.initialize();
        return new AsyncEventBus("async-bus", executor);
    }
}

自动注册所有订阅者

java 复制代码
/**
 * 自动将所有标记了 @EventSubscriber 的 Bean 注册到 EventBus.
 */
@Component
public class EventBusAutoRegister implements ApplicationListener<ContextRefreshedEvent> {

    @Resource
    private EventBus syncEventBus;
    @Resource
    private AsyncEventBus asyncEventBus;
    @Resource
    private ApplicationContext applicationContext;

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        // 扫描所有带 @EventSubscriber 注解的 Bean
        Map<String, Object> subscribers = 
            applicationContext.getBeansWithAnnotation(EventSubscriber.class);
        
        for (Object subscriber : subscribers.values()) {
            EventSubscriber annotation = subscriber.getClass().getAnnotation(EventSubscriber.class);
            if (annotation.async()) {
                asyncEventBus.register(subscriber);
            } else {
                syncEventBus.register(subscriber);
            }
        }
    }
}

// 自定义注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component  // 让Spring扫描到
public @interface EventSubscriber {
    boolean async() default false;
}

使用

java 复制代码
// 订阅者 --- 加上自定义注解即可自动注册
@EventSubscriber(async = true)
public class OrderNotifySubscriber {

    @Resource
    private SmsService smsService;

    @Subscribe
    public void onOrderCreated(OrderCreatedEvent event) {
        smsService.send(event.getPhone(), "下单成功");
    }
}

@EventSubscriber
public class OrderLogSubscriber {

    @Subscribe
    public void onOrderCreated(OrderCreatedEvent event) {
        log.info("订单创建, orderId:{}", event.getOrderId());
    }
}

// 发布者
@Service
public class OrderService {

    @Resource
    private EventBus syncEventBus;

    public void createOrder(OrderDto dto) {
        Order order = orderRepository.save(dto);
        syncEventBus.post(new OrderCreatedEvent(order));
    }
}

九、一个订阅者处理多种事件

一个类中可以有多个 @Subscribe 方法,根据参数类型自动匹配:

java 复制代码
@EventSubscriber
public class NotificationSubscriber {

    @Subscribe
    public void onOrderCreated(OrderCreatedEvent event) {
        sendSms(event.getPhone(), "下单成功,订单号:" + event.getOrderId());
    }

    @Subscribe
    public void onOrderShipped(OrderShippedEvent event) {
        sendSms(event.getPhone(), "已发货,物流单号:" + event.getTrackingNo());
    }

    @Subscribe
    public void onOrderDelivered(OrderDeliveredEvent event) {
        sendSms(event.getPhone(), "已签收,请确认收货");
    }

    @Subscribe
    public void onPaymentFailed(PaymentFailedEvent event) {
        sendSms(event.getPhone(), "支付失败,请重试");
    }
}

十、Guava EventBus vs Spring Event

维度 Guava EventBus Spring Event
依赖 guava 库 spring-context
注解 @Subscribe @EventListener
注册方式 手动 register() 自动扫描
事务感知 ❌ 无 @TransactionalEventListener
异步支持 AsyncEventBus @Async
事件继承 ✅ 自动匹配父类 ✅ 支持
条件过滤 ❌ 需自行判断 condition 属性
DeadEvent ✅ 有 ❌ 无
异常隔离 ✅ 一个失败不影响其他 ❌ 默认会传播(同步时)
使用场景 非Spring项目或需要精细控制 Spring项目(推荐)

选择建议

复制代码
已有Spring项目 → 直接用 Spring Event(@EventListener)
  - 无需额外依赖
  - 自动注册
  - 支持事务感知

非Spring项目 / 需要更轻量 → 用 Guava EventBus
  - 不依赖Spring容器
  - 有 DeadEvent 机制
  - 异常隔离更好

十一、注意事项与陷阱

1. @Subscribe 方法必须是 public

java 复制代码
// ✗ 错误:private方法不会被识别
@Subscribe
private void handle(MyEvent event) { }

// ✓ 正确
@Subscribe
public void handle(MyEvent event) { }

2. 参数类型决定订阅什么事件

java 复制代码
// 订阅 String 类型的事件
@Subscribe
public void handle(String msg) { }

// 订阅 OrderEvent 类型的事件
@Subscribe
public void handle(OrderEvent event) { }

// ✗ 不能有多个参数
@Subscribe
public void handle(OrderEvent event, String extra) { }  // 编译不报错但运行时不生效

3. 同步 EventBus 中不要做耗时操作

java 复制代码
// ✗ 差:阻塞了发布者线程
@Subscribe
public void handle(OrderEvent event) {
    Thread.sleep(10000);  // 发布者会被阻塞10秒
    sendEmail(event);
}

// ✓ 好:用 AsyncEventBus 或内部异步处理
@Subscribe
public void handle(OrderEvent event) {
    CompletableFuture.runAsync(() -> sendEmail(event));
}

4. 忘记 register 是最常见的bug

java 复制代码
EventBus bus = new EventBus();
// 忘记 bus.register(subscriber) → post 后没有任何反应
bus.post(new MyEvent());  // 静悄悄,没人处理 → 变成 DeadEvent

十二、实际项目中的完整示例

java 复制代码
/**
 * 电商系统中使用 Guava EventBus 解耦下单后的多个操作.
 */

// === 事件定义 ===
@Data
@AllArgsConstructor
public class OrderCreatedEvent {
    private String orderId;
    private Integer userId;
    private BigDecimal amount;
    private String phone;
    private List<String> productNames;
}

// === EventBus 配置 ===
@Configuration
public class EventBusConfig {
    @Bean
    public AsyncEventBus orderEventBus() {
        return new AsyncEventBus("order-events",
            Executors.newFixedThreadPool(4, new ThreadFactoryBuilder()
                .setNameFormat("order-event-%d").build()));
    }
}

// === 订阅者们 ===
@Component
public class OrderSmsSubscriber {
    @Resource private SmsService smsService;

    @Subscribe
    public void handle(OrderCreatedEvent event) {
        smsService.send(event.getPhone(), 
            String.format("下单成功!订单号%s,金额%.2f元", 
                event.getOrderId(), event.getAmount()));
    }
}

@Component
public class OrderPointsSubscriber {
    @Resource private PointsService pointsService;

    @Subscribe
    public void handle(OrderCreatedEvent event) {
        int points = event.getAmount().intValue();  // 1元=1积分
        pointsService.add(event.getUserId(), points);
    }
}

@Component
public class OrderStatisticsSubscriber {
    @Resource private StatisticsService statisticsService;

    @Subscribe
    public void handle(OrderCreatedEvent event) {
        statisticsService.incrementDailyOrderCount();
        statisticsService.addDailyRevenue(event.getAmount());
    }
}

@Component
public class OrderInventorySubscriber {
    @Resource private InventoryService inventoryService;

    @Subscribe
    public void handle(OrderCreatedEvent event) {
        for (String product : event.getProductNames()) {
            inventoryService.decrementStock(product, 1);
        }
    }
}

// === 自动注册 ===
@Component
public class OrderEventBusRegistrar {
    @Resource private AsyncEventBus orderEventBus;
    @Resource private OrderSmsSubscriber smsSubscriber;
    @Resource private OrderPointsSubscriber pointsSubscriber;
    @Resource private OrderStatisticsSubscriber statsSubscriber;
    @Resource private OrderInventorySubscriber inventorySubscriber;

    @PostConstruct
    public void init() {
        orderEventBus.register(smsSubscriber);
        orderEventBus.register(pointsSubscriber);
        orderEventBus.register(statsSubscriber);
        orderEventBus.register(inventorySubscriber);
    }
}

// === 发布者(订单服务) ===
@Service
public class OrderService {
    @Resource private OrderRepository orderRepository;
    @Resource private AsyncEventBus orderEventBus;

    @Transactional
    public Order createOrder(OrderDto dto) {
        Order order = orderRepository.save(dto);
        
        // 发布事件 --- 不关心后续处理
        orderEventBus.post(new OrderCreatedEvent(
            order.getId(), order.getUserId(), order.getAmount(),
            order.getPhone(), order.getProductNames()));
        
        return order;
    }
    // 未来新增"下单后推送APP通知"?
    // 只需要新增一个 Subscriber + register,OrderService 零修改
}

十三、总结

复制代码
Guava EventBus 是什么:
  Google Guava 提供的进程内轻量级事件总线
  通过 @Subscribe 注解声明订阅者
  通过 post() 发布事件

核心特点:
  轻量(几个类就实现了)
  进程内(不能跨JVM)
  支持同步和异步
  事件继承匹配
  订阅者异常隔离
  DeadEvent 检测未处理事件

适用场景:
  同一应用内模块解耦
  一个操作需要触发多个后续处理
  需要灵活增减后续处理逻辑

不适用场景:
  跨服务通信(用MQ)
  需要事务感知(用Spring Event)
  需要消息持久化(用MQ)

在Spring项目中:
  优先用 Spring Event(@EventListener)
  Guava EventBus 更适合非Spring环境或需要DeadEvent/异常隔离等特性时使用
相关推荐
月华路2 天前
Guava 集合框架与 JDK 集合的分工:为什么工程里还需要 Guava
java·guava
蜡台10 天前
Guava 库新手快速上手与实战指南
java·guava
豆瓣鸡1 个月前
Guava RateLimiter 限流实战:从令牌桶原理到 Nacos 动态配置
spring boot·微服务·nacos·guava
音符犹如代码1 个月前
后端视角看 EventBus:发布订阅总线的原理、场景与用法
java·spring boot·guava
徐寿春2 个月前
什么是数据倾斜
java·guava
QuZero2 个月前
Guava Cache Deep Dive
java·后端·算法·guava
西凉的悲伤2 个月前
Guava类库——Range连续区间
java·算法·guava
拼尽全力前进3 个月前
Guava Cache vs Caffeine 面试详解
面试·职场和发展·guava
大梦谁先觉i3 个月前
Milvus 向量数据库:原理详解、离线部署、可视化配置与全套实操教程
transformer·guava