Guava——事件总线

事件总线

    • 1、概述
    • 2、核心API
    • 3、使用示例
      • [3.1、基础同步 EventBus 使用示例](#3.1、基础同步 EventBus 使用示例)
      • [3.2、异步 AsyncEventBus 与异常处理](#3.2、异步 AsyncEventBus 与异常处理)
    • 4、进阶示例
      • [4.1、事件继承分发机制(Polymorphic Dispatching)](#4.1、事件继承分发机制(Polymorphic Dispatching))
      • [4.2、高并发异步总线与 @AllowConcurrentEvents](#4.2、高并发异步总线与 @AllowConcurrentEvents)
      • 4.3、真实业务场景应用

1、概述

Guava EventBus 是 Google Guava 提供的进程内发布-订阅(Pub/Sub)事件总线,通过注解和反射机制实现组件解耦,避免了编写大量观察者模式接口模板代码。

类 / 注解 / 接口 类型 说明
EventBus 同步事件总线。事件发布(post)与订阅者执行在同一个线程中同步运行
AsyncEventBus 异步事件总线(继承自 EventBus)。配合线程池 Executor,异步分发和执行订阅逻辑
@Subscribe 注解 标记在订阅者方法上。方法必须是 public void 且只能包含一个参数(即监听的事件类型)
@AllowConcurrentEvents 注解 配合 AsyncEventBus 使用。标记方法是线程安全的,允许总线并发调用该方法而无需加锁
DeadEvent 当发布的事件在总线中找不到任何匹配的订阅者时,会被包装为 DeadEvent 重新发布
SubscriberExceptionHandler 接口 订阅者方法执行抛出未捕获异常时的自定义处理器

EventBus 核心方法

  • register(Object object):注册监听者对象(自动扫描对象内所有带有 @Subscribe 的方法)。
  • unregister(Object object):注销监听者对象。
  • post(Object event):向总线发布事件,自动匹配并触发消费该事件(及其父类/接口)的所有订阅者。

2、核心API

1. API 全景与方法签名详解

EventBus 与 AsyncEventBus 构造方法与 API

类名 方法 / 构造器 作用与细节
EventBus EventBus() 创建标识符为 "default" 的同步总线
EventBus(String identifier) 创建指定名称的同步总线(方便日志定位)
EventBus(SubscriberExceptionHandler handler) 传入自定义异常处理器创建总线
register(Object listener) 扫描目标对象中所有带 @Subscribepublic 方法并注册
unregister(Object listener) 注销指定对象的监听(若未注册过会抛 IllegalArgumentException
post(Object event) 同步发布事件;按注册顺序在当前线程依次调用匹配的订阅者
identifier() 获取总线的名称/标识符
AsyncEventBus AsyncEventBus(String id, Executor executor) 指定标识符和执行线程池
AsyncEventBus(Executor executor) 匿名异步总线,使用指定线程池调度事件
AsyncEventBus(Executor exec, SubscriberExceptionHandler handler) 指定线程池与异常处理器的异步总线

2. 异常追踪与死事件 API

类名 方法签名 作用说明
SubscriberExceptionContext EventBus getEventBus() 获取抛出异常时所在的总线对象
Object getEvent() 获取当前引发异常的事件实例
Object getSubscriber() 获取当前抛出异常的订阅者监听类对象
Method getSubscriberMethod() 获取触发异常的 java.lang.reflect.Method 实例
DeadEvent Object getSource() 获取发送该未消费事件 processing 的 EventBus 实例
Object getEvent() 获取原先发送但未被任何订阅者接收的原始事件对象

3、使用示例

3.1、基础同步 EventBus 使用示例

步骤 1:定义事件对象(普通的 POJO)

java 复制代码
// 订单创建事件
public class OrderCreatedEvent {
    private final String orderId;

    public OrderCreatedEvent(String orderId) {
        this.orderId = orderId;
    }

    public String getOrderId() {
        return orderId;
    }
}

步骤 2:定义订阅者(Listener)

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

public class OrderEventListener {

    // 监听特定订单创建事件
    @Subscribe
    public void onOrderCreated(OrderCreatedEvent event) {
        System.out.println("[邮件服务] 收到订单创建事件,发送确认邮件,订单号: " + event.getOrderId());
    }

    // 监听死事件(没有找到任何匹配订阅者的事件)
    @Subscribe
    public void handleDeadEvent(DeadEvent deadEvent) {
        System.out.println("警告:收到死事件,未被任何订阅者消费: " + deadEvent.getEvent());
    }
}

步骤 3:注册并发布事件

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

public class EventBusMain {
    public static void main(String[] args) {
        // 1. 创建同步事件总线
        EventBus eventBus = new EventBus("OrderBus");

        // 2. 注册监听者
        OrderEventListener listener = new OrderEventListener();
        eventBus.register(listener);

        // 3. 发布事件(匹配到 onOrderCreated 方法执行)
        eventBus.post(new OrderCreatedEvent("ORD_10001"));

        // 4. 发布未配置订阅者的事件(会触发 DeadEvent)
        eventBus.post("Unsubscribed String Event");

        // 5. 注销监听者
        eventBus.unregister(listener);
    }
}
java 复制代码
[邮件服务] 收到订单创建事件,发送确认邮件,订单号: ORD_10001
警告:收到死事件,未被任何订阅者消费: Unsubscribed String Event

3.2、异步 AsyncEventBus 与异常处理

在生产环境中,消费逻辑耗时较长时推荐使用 AsyncEventBus,并显式配置 SubscriberExceptionHandler 捕获订阅者内部抛出的运行时异常。

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

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class AsyncEventBusExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(4);

        // 自定义订阅者未捕获异常处理器
        SubscriberExceptionHandler exceptionHandler = (exception, context) -> {
            System.err.printf("执行订阅者方法 [%s] 抛出异常,事件内容: %s,原因: %s%n",
                    context.getSubscriberMethod().getName(),
                    context.getEvent(),
                    exception.getMessage());
        };

        // 创建异步事件总线
        AsyncEventBus asyncEventBus = new AsyncEventBus("AsyncOrderBus", executor, exceptionHandler);

        // 注册会触发异常的订阅者
        asyncEventBus.register(new Object() {
            @Subscribe
            public void handle(String message) {
                System.out.println("异步处理线程: " + Thread.currentThread().getName());
                throw new RuntimeException("模拟处理失败");
            }
        });

        // 发布消息
        asyncEventBus.post("Async Test Message");

        executor.shutdown();
    }
}

4、进阶示例

4.1、事件继承分发机制(Polymorphic Dispatching)

Guava EventBus 支持多态分发:当发布一个子类事件时,订阅了该子类及其父类或实现接口的所有订阅者都会按顺序收到该事件。

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

// 1. 定义事件继承结构
interface LoggableEvent { String getMessage(); }

class BaseOrderEvent implements LoggableEvent {
    private final String orderId;
    public BaseOrderEvent(String orderId) { this.orderId = orderId; }
    public String getOrderId() { return orderId; }
    @Override public String getMessage() { return "Order " + orderId; }
}

class PaySuccessEvent extends BaseOrderEvent {
    private final double amount;
    public PaySuccessEvent(String orderId, double amount) {
        super(orderId);
        this.amount = amount;
    }
    public double getAmount() { return amount; }
}

// 2. 订阅者定义不同粒度的监听方法
class MultiEventListener {
    // 监听所有实现 LoggableEvent 接口的事件
    @Subscribe
    public void onAnyLoggable(LoggableEvent event) {
        System.out.println("[日志审计中心] 记录通用事件: " + event.getMessage());
    }

    // 监听所有订单基础事件
    @Subscribe
    public void onBaseOrder(BaseOrderEvent event) {
        System.out.println("[订单中心] 触发订单通用处理,单号: " + event.getOrderId());
    }

    // 仅监听支付成功事件
    @Subscribe
    public void onPaySuccess(PaySuccessEvent event) {
        System.out.println("[财务中心] 收到支付成功,金额: " + event.getAmount());
    }
}

public class InheritanceExample {
    public static void main(String[] args) {
        EventBus bus = new EventBus("InheritanceBus");
        bus.register(new MultiEventListener());

        // 发布子类 PaySuccessEvent 消息
        // 会依次触发:onPaySuccess -> onBaseOrder -> onAnyLoggable
        bus.post(new PaySuccessEvent("ORD-2026-001", 299.0));
    }
}

4.2、高并发异步总线与 @AllowConcurrentEvents

在 AsyncEventBus 下,默认对同一个监听者对象的订阅方法调用会加锁串行化。若订阅者方法是线程安全的,可加上 @AllowConcurrentEvents 注解来解除锁限制,提升高并发吞吐量。

java 复制代码
import com.google.common.eventbus.AllowConcurrentEvents;
import com.google.common.eventbus.AsyncEventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.eventbus.SubscriberExceptionHandler;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class HighConcurrencyExample {

    // 无状态/线程安全的订阅者
    static class MetricsCollector {
        private final AtomicInteger counter = new AtomicInteger();

        @Subscribe
        @AllowConcurrentEvents // 允许线程池中的多条线程同时并发执行此方法,提高吞吐量
        public void onMetric(String metricName) {
            int current = counter.incrementAndGet();
            System.out.println(Thread.currentThread().getName() + " 处理指标: " + metricName + " | 当前总计: " + current);
        }
    }

    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(4);

        // 自定义详细的异常日志上下文提取
        SubscriberExceptionHandler exceptionHandler = (exception, context) -> {
            System.err.println("--- 事件处理异常 ---");
            System.err.println("总线标识: " + context.getEventBus().identifier());
            System.err.println("发生类: " + context.getSubscriber().getClass().getName());
            System.err.println("发生方法: " + context.getSubscriberMethod().getName());
            System.err.println("事件对象: " + context.getEvent());
            exception.printStackTrace();
        };

        AsyncEventBus asyncBus = new AsyncEventBus("MetricsBus", threadPool, exceptionHandler);
        asyncBus.register(new MetricsCollector());

        // 模拟多线程同时向总线 post 消息
        for (int i = 0; i < 5; i++) {
            final int index = i;
            new Thread(() -> asyncBus.post("qps_metric_" + index)).start();
        }

        threadPool.shutdown();
    }
}

4.3、真实业务场景应用

通过 EventBus 将"创建订单"主逻辑与"发送短信"、"更新库存"、"发放积分"等旁路业务解耦:

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

class OrderService {
    private final EventBus eventBus;

    public OrderService(EventBus eventBus) {
        this.eventBus = eventBus;
    }

    public void createOrder(String orderId, String userId, long amount) {
        // 1. 执行核心业务逻辑:写订单数据库
        System.out.println(">>> 主流程:订单 " + orderId + " 创建成功,保存数据库");

        // 2. 发布事件解耦旁路逻辑
        eventBus.post(new OrderCompletedEvent(orderId, userId, amount));
    }
}

// 旁路服务 1:短信通知
class SmsNotificationService {
    @Subscribe
    public void sendSms(OrderCompletedEvent event) {
        System.out.println("旁路服务:向用户 " + event.userId() + " 发送短信通知");
    }
}

// 旁路服务 2:积分扣减
class PointsService {
    @Subscribe
    public void addPoints(OrderCompletedEvent event) {
        System.out.println("旁路服务:用户 " + event.userId() + " 增加积分: " + (event.amount() / 10));
    }
}

record OrderCompletedEvent(String orderId, String userId, long amount) {}

public class DomainDecoupleDemo {
    public static void main(String[] args) {
        EventBus bus = new EventBus("DomainBus");
        
        // 注册各种独立微服务/组件
        bus.register(new SmsNotificationService());
        bus.register(new PointsService());

        OrderService orderService = new OrderService(bus);
        orderService.createOrder("ORD_20260903", "USER_888", 50000);
    }
}
相关推荐
en.en..1 小时前
Ubuntu嵌入式开发 export环境变量
java·开发语言·数据库
gf13211111 小时前
【python_回复邮件】
android·java·python
攻城有术1 小时前
专项攻克——重写 Redis 依赖包方法的 6 种实现方式
java·数据库·redis·bootstrap
跨境数据猎手1 小时前
京东开放平台商品详情接口(jd.item_get):签名、缓存与批量采集
java·spring·缓存
比兔代理1 小时前
住宅代理IP的出口节点调度:ASN、BGP 与 IP 段信誉机制解析
linux·服务器·网络
AAA代码批发商1 小时前
DAYS 38 TCP并发服务器模型详解
linux·网络·笔记·学习
Quanqiucard1 小时前
监控球机联网总掉线?问题可能出在物联网卡上!
网络·物联网
三8441 小时前
应急响应之服务、文件痕迹、网络、日志排查 + 工具清单
网络·应急响应·日志排查·文件痕迹排查·应急响应报告模板·服务排查
晏宁科技YaningAI2 小时前
企业通信系统技术规划方法:从业务需求到可演进架构
网络·性能优化·架构·gateway·paas