后端视角看 EventBus:发布订阅总线的原理、场景与用法

简介: 做 Android 或 Java 想解耦组件通信的开发者,本文用后端视角讲清 EventBus 是什么、场景与用法,并对比 Spring、Guava

大家好,我是程序员天天困。今天聊一个能让你少写一大堆回调的东西——EventBus,一个在 Android 和 Java 里都挺火的事件发布订阅库。我自己做后端多一些,但这东西在 Android 用得最广,所以咱们两边都照顾着讲,争取你写哪端都能用得上。话不多说,直接开始。

一、EventBus 到底是干什么的

EventBus 不是什么新发明,它就是把「发布-订阅」模式做成一个开箱即用的总线,让两个模块不用互相认识就能通信。

EventBus(发布订阅事件总线):greenrobot 出品的开源库,用发布-订阅模式解耦组件通信——发布者只管把事件丢进总线,订阅者通过注解方法接收,两者互不持有引用。你可以把它理解成「代码里的广播站」。

EventBus 发布订阅原理:发布者 post 事件经总线分发到订阅者

广播站的比喻最贴切:拿话筒的人喊一嗓子,所有调到这个频道的接收器都能听到,喊的人根本不用知道谁在听。回到后端,你写 Spring 时用过的 @EventListenerApplicationEventPublisher,或者 Guava 的 EventBus,乃至 Kafka、RabbitMQ,都是同一套发布订阅思想的不同重量级实现。EventBus 是其中最轻的一个:一个约 60k 的 jar,不依赖任何中间件,在进程内同步或异步分发,延迟在微秒级。

它的运行机制简单到一句话:发布者调 post 把事件丢进 EventBus,EventBus 根据事件类型查到所有订阅了这个类型的方法,依次调用。就这么直接,发布者不关心谁来收,订阅者也不关心谁发的。

二、为什么需要它:组件通信的痛

那有人就会问了,Android 自己不是有通信机制吗,为啥还要再造一个 EventBus?这得先看看原生的那套东西痛在哪。

Android 想让组件之间传消息,最常用的就两样:Handler 和 BroadcastReceiver。Handler 用来切线程、投递消息,BroadcastReceiver 用来发系统级广播。能用是能用,但写起来烦——一堆样板代码不说,发消息的还得知道收消息的是谁,两边经常要互相持有引用,组件一多,引用关系就缠成一团。

最常见的场景:你在子线程里跑接口请求,数据回来要刷新 UI,得拿 Handler 切回主线程;两个 Fragment 要互换数据,得借 Activity 当中间人,或者写个 Listener 接口回调。项目小的时候忍忍就过去了,业务一膨胀,这种回调套回调、引用套引用的写法,代码量和耦合度一起失控。后端其实一个毛病——Service 之间互相 @Autowired,最后织成一张谁也拆不动的网,根子都是发布者把「我要通知谁」写死在了自己代码里。

EventBus 的解法很简单:A 不认识 B/C/D,它只认识 EventBus。谁关心谁自己订阅,发布者彻底从「通知谁」里解放出来,新增一个订阅者,发布者一行代码都不用改。

下面这张图把两种方式摆在一起——左边是组件之间互相持有引用、回调层层传递;右边是发布者只对总线 post,订阅者各自从总线取,谁也不认识谁。

EventBus 解耦对比:旧方式组件互相持有引用 vs EventBus 发布订阅解耦

可能有人会问:那我后端直接上 Kafka、RabbitMQ 不就完了,为啥还要一个进程内的 EventBus?

两者解决的不是同一个量级的问题。MQ 是跨进程、跨机器的异步消息,带持久化和集群;EventBus 是单进程内的方法调用级通信,没有网络、没有序列化开销,延迟在微秒级。同一个 JVM 内部模块解耦,上 MQ 是杀鸡用牛刀;跨服务通信,EventBus 又鞭长莫及。各管一段,别混用。

三、三步上手:在 Spring Boot 里用 EventBus

EventBus 的全部 API 浓缩成三步——定义事件、注册订阅、发送事件,背下来就能用。它不绑 Android,Spring Boot 后端一样能跑,把依赖换成 eventbus-java 就行。如果你团队已经定了用 EventBus,或者要和 Android 端共用一套事件模型,下面这套 Spring Boot 写法直接抄;至于要不要在 Spring 里上它,选型建议放在第六节。

第一步:引入 Maven 依赖。pom.xml 里加这一段:

<!-- 以 Maven Central 3.3.1 为准;Spring Boot 2.x 注解用 javax,3.x 用 jakarta -->
<dependency>
    <groupId>org.greenrobot</groupId>
    <artifactId>eventbus-java</artifactId>
    <version>3.3.1</version>
</dependency>

第二步:定义事件 + 写订阅者。 事件就是一个普通 POJO,不继承任何基类;订阅者用 @Subscribe 标注接收方法,方法必须 public、有且仅有一个参数:

public class LoginEvent {
   
    private final String username;
    public LoginEvent(String username) {
    this.username = username; }
    public String getUsername() {
    return username; }
}
@Component
public class UserModule {
   
    @Subscribe(threadMode = ThreadMode.POSTING)
    public void onLogin(LoginEvent event) {
   
        System.out.println("用户 " + event.getUsername() + " 已登录");
    }

    @PostConstruct           // Bean 启动时注册
    public void init() {
    EventBus.getDefault().register(this); }

    @PreDestroy              // Bean 销毁时注销
    public void destroy() {
    EventBus.getDefault().unregister(this); }
}

把订阅者声明成 Spring 的 @Component,再用 @PostConstruct 注册、@PreDestroy 注销,register 和 unregister 就跟着 Bean 生命周期自动成对,不用你手动记着去调。

第三步:发送事件。 在任何 Service 里直接 post,发布者完全不需要知道谁在监听:

@Service
public class LoginService {
   
    public void login(String username) {
   
        // 业务处理……
        EventBus.getDefault().post(new LoginEvent(username));
    }
}

我见过太多线上 OOM,根因就是 register 了却没 unregister——EventBus 持有订阅者引用,对象回收不掉。所以哪怕图省事,也务必让注册和注销跟着生命周期走,别只 register 不注销。

下面这张时序图展示了从 post 到被订阅方法处理的完整链路:发布者 post 后,EventBus 按事件类型查订阅表,再按线程模式决定同步直调还是丢进线程池。

EventBus 一次事件投递时序:post 到订阅方法处理流程

四、@Subscribe 三个关键参数:线程模式、粘性事件、优先级

@Subscribe 注解的三个参数不是花架子,它们决定了事件在哪个线程跑、能不能收到「早到」的事件、谁先收到。

@Subscribe:EventBus 的订阅方法注解,标注「这个方法要接收什么类型的事件」,可指定线程模式、是否粘性、优先级。被注解的方法要求 public 修饰、有且仅有一个参数。

粘性事件(Sticky Event)postSticky 发出后会被 EventBus 缓存下来的事件,之后新注册的订阅者也能收到它。你可以理解成「迟到的订阅者也能拿到上一条广播」。

线程模式(ThreadMode)决定了订阅方法在哪个线程被执行。纯 Java 后端主要用 POSTING 和 ASYNC;MAIN 系列依赖 Android 主线程,后端用不到:

ThreadMode 在哪执行 纯 Java 能用 典型场景
POSTING post 所在线程直接同步调用 不耗时的事件,最常用
ASYNC 永远丢进线程池异步执行 订阅者要做耗时 IO
BACKGROUND post 在主线程则入池,在子线程则直接调 轻量后台任务
MAIN Android 主线程执行 更新 UI
MAIN_ORDERED 主线程,按顺序非阻塞 多个 UI 更新串行

粘性事件有个坑要单独说:缓存的粘性事件不会自动消失,重复 postSticky 同类型会覆盖旧的,但如果你忘了 removeStickyEvent,换场景后新订阅者可能收到一条本不该收的「历史事件」。我的习惯是粘性事件用完立刻 removeStickyEvent,别让缓存兜着脏数据。

优先级 priority 是个 int,数字越大越先收到,且只在同一线程模式下才有意义。

五、什么场景下用 EventBus

EventBus 适合「一对多、发布者不想知道订阅者是谁」的场景;不适合「调用方需要返回值」或「强顺序依赖」的场景。后者本质上是在做方法调用,硬套事件总线只会让链路更难追踪。

几类典型场景:

  1. 全局状态广播:登录登出、网络断连恢复——一处 post,多处 UI 或模块各自响应。
  2. 跨层组件通信:Fragment 之间、Fragment 与 Activity、Service 与页面,不再借 Activity 中转。
  3. 后端模块解耦:领域事件的轻量版——下单成功后通知积分、库存、推送模块各自处理,下单服务不关心下游有谁。
  4. 配置热更新:配置中心拉到新值后 post 一个 ConfigChangedEvent,各模块自己刷新。

可能有人会问:后端真要用 EventBus 吗,还是直接 Spring @EventListener 更香?

老实说,纯 Spring 后端我更推荐用 Spring 自带的 @EventListener:零额外依赖、和容器生命周期天然集成、还支持 @TransactionalEventListener 跟着事务走。EventBus 的甜区在 Android 和非 Spring 的纯 Java 工程——没有 Spring 容器、又想要个轻量发布订阅,它比手撸观察者模式省心。要是跨进程,老老实实上 MQ。

六、后端视角的取舍:EventBus vs Guava vs Spring

选哪个,看你在不在 Spring 容器里、要不要跨进程、对依赖体积多敏感。

方案 依赖 线程模型 跨进程 适合谁
greenrobot EventBus ~60k jar ThreadMode 五种 Android / 非 Spring 纯 Java(Spring 里也能跑)
Guava EventBus Guava 整包 同步或异步二选一 已用 Guava 的纯 Java 项目
Spring @EventListener Spring 容器 同步,可配异步 任何 Spring 后端
Kafka / RabbitMQ 独立中间件 异步 跨服务、跨进程

EventBus 也不是银弹,几个老问题一直都在:事件类会越定义越多,一个项目几十个 Event 类很常见;订阅方法靠反射查找(编译时索引能优化,但要配 annotation processor);忘了 unregister 就内存泄漏;最麻烦的是事件流难追踪——post 一条事件,到底谁会响应、什么顺序,得全局搜,调试链路偏长。这些都是它换来解耦的代价。

写法上以 Maven Central 的 org.greenrobot:eventbus:3.3.1(2021 年最后大版本)为准,长期稳定。Android 新项目也有人用 Kotlin 的 Flow / Channel 替代,但就「最快上手、几行解耦」而言,EventBus 依然是门槛最低的方案之一。

后端要不要上 EventBus,我的判断就一句:在 Spring 里别折腾,@EventListener 已经够用;写 Android 或纯 Java 又想轻量解耦,EventBus 依然是几行代码搞定、最省心的那个。跨进程的事,交给 MQ,别让一条进程内总线去干它不该干的活。

相关文章
408王道计算机组成原理强化——输入输出系统大题(I/O)
408王道计算机组成原理强化——输入输出系统大题(I/O)
991 1
408王道计算机组成原理强化——输入输出系统大题(I/O)
|
存储 easyexcel Java
阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了
阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了
阿里easyexcel解析百万级大数据量的Excel表格,看这一篇文章就够了
|
8月前
|
消息中间件 NoSQL Java
拒绝频繁写库!SpringBoot 整合 BufferTrigger 实现高性能“流量聚合”
本文介绍如何用SpringBoot整合BufferTrigger实现高性能流量聚合,解决高并发下频繁写库的痛点。通过快手开源的BufferTrigger组件,可将大量数据库操作合并为批量执行,显著提升I/O效率,适用于计数、埋点、状态同步等场景,兼具高性能与低延迟。
697 145
|
1月前
|
存储 人工智能 JSON
OpenCode 替代 Claude Code:传闻阿里内部全面禁用 Claude Code!
Claude Code 封号潮网传阿里禁用,opencode 成开源替代首选。本文讲透 npm 安装与 CC Switch、手动两种方式迁移 MCP、Agent。
OpenCode 替代 Claude Code:传闻阿里内部全面禁用 Claude Code!
|
22天前
|
人工智能 前端开发 自动驾驶
Loop Engineering 已死?Graph Engineering 是什么?我好像在哪里见过
Graph Engineering 刷屏,后端第一反应是这不工作流引擎吗?本文聊它到底是什么、Loop 真死了吗、什么场景才值得上
Loop Engineering 已死?Graph Engineering 是什么?我好像在哪里见过
|
2月前
|
人工智能 JSON 测试技术
Harness Engineering 是什么?AI 编程工程化的三次进化
Harness Engineering 凭什么刷屏 AI 圈?从提示词到上下文再到 Harness,一文讲透它的来龙去脉和五大核心模块。
缓存 JavaScript Shell
875 1
|
2月前
|
人工智能 监控 自动驾驶
Loop Engineering 实战:/goal 命令让 AI 自己写完整项目
Loop Engineering 让 AI 自己循环干活。本文用 Claude Code /goal 带你从零搭项目,跑通自动开发全流程——设定目标,循环搞定。
Loop Engineering 实战:/goal 命令让 AI 自己写完整项目
|
2月前
|
Shell API 开发工具
Claude Code 实战:Agent Skills
面向已用 Claude Code 写代码的开发者,讲清 Skills 三层结构与完整实操路径,帮你把重复工作流封装成可复用、可 Review 的技能包。
Claude Code 实战:Agent Skills
|
29天前
|
人工智能 运维 IDE
Qoder CN(原通义灵码)全维度详解:产品矩阵、版本区分与技术适配实操手册
2026年阿里云完成旗下代码工具品牌战略升级,原通义灵码正式更名为**Qoder CN**。本次更名并非简单品牌替换,而是产品定位、底层架构、产品形态、计费体系的全方位迭代升级,从单一IDE代码补全插件,进化为覆盖编码、办公、终端、云端协同的全栈智能研发AI智能体矩阵。整套产品依托本土化大模型底座,兼顾数据安全合规要求,面向编程学习者、独立开发者、中小研发团队、金融政务等高合规企业打造分层版本,覆盖个人练手、全职开发、企业规模化项目研发全场景。本文将从产品全形态矩阵、四大版本功能差异、底层技术兼容能力、核心智能体能力、Credits计费体系、分人群精准选型六大板块完整拆解,附带多端安装配置实操
318 0