行业资讯
后端视角看 EventBus:发布订阅总线的原理、场景与用法
大家好我是程序员天天困。今天聊一个能让你少写一大堆回调的东西——EventBus一个在 Android 和 Java 里都挺火的事件发布订阅库。我自己做后端多一些但这东西在 Android 用得最广所以咱们两边都照顾着讲争取你写哪端都能用得上。话不多说直接开始。一、EventBus 到底是干什么的EventBus 不是什么新发明它就是把「发布-订阅」模式做成一个开箱即用的总线让两个模块不用互相认识就能通信。EventBus发布订阅事件总线greenrobot 出品的开源库用发布-订阅模式解耦组件通信——发布者只管把事件丢进总线订阅者通过注解方法接收两者互不持有引用。你可以把它理解成「代码里的广播站」。广播站的比喻最贴切拿话筒的人喊一嗓子所有调到这个频道的接收器都能听到喊的人根本不用知道谁在听。回到后端你写 Spring 时用过的EventListener配ApplicationEventPublisher或者 Guava 的EventBus乃至 Kafka、RabbitMQ都是同一套发布订阅思想的不同重量级实现。EventBus 是其中最轻的一个一个约 60k 的 jar不依赖任何中间件在进程内同步或异步分发延迟在微秒级。它的运行机制简单到一句话发布者调post把事件丢进 EventBusEventBus 根据事件类型查到所有订阅了这个类型的方法依次调用。就这么直接发布者不关心谁来收订阅者也不关心谁发的。二、为什么需要它组件通信的痛那有人就会问了Android 自己不是有通信机制吗为啥还要再造一个 EventBus这得先看看原生的那套东西痛在哪。Android 想让组件之间传消息最常用的就两样Handler 和 BroadcastReceiver。Handler 用来切线程、投递消息BroadcastReceiver 用来发系统级广播。能用是能用但写起来烦——一堆样板代码不说发消息的还得知道收消息的是谁两边经常要互相持有引用组件一多引用关系就缠成一团。最常见的场景你在子线程里跑接口请求数据回来要刷新 UI得拿 Handler 切回主线程两个 Fragment 要互换数据得借 Activity 当中间人或者写个 Listener 接口回调。项目小的时候忍忍就过去了业务一膨胀这种回调套回调、引用套引用的写法代码量和耦合度一起失控。后端其实一个毛病——Service 之间互相Autowired最后织成一张谁也拆不动的网根子都是发布者把「我要通知谁」写死在了自己代码里。EventBus 的解法很简单A 不认识 B/C/D它只认识 EventBus。谁关心谁自己订阅发布者彻底从「通知谁」里解放出来新增一个订阅者发布者一行代码都不用改。下面这张图把两种方式摆在一起——左边是组件之间互相持有引用、回调层层传递右边是发布者只对总线 post订阅者各自从总线取谁也不认识谁。可能有人会问那我后端直接上 Kafka、RabbitMQ 不就完了为啥还要一个进程内的 EventBus两者解决的不是同一个量级的问题。MQ 是跨进程、跨机器的异步消息带持久化和集群EventBus 是单进程内的方法调用级通信没有网络、没有序列化开销延迟在微秒级。同一个 JVM 内部模块解耦上 MQ 是杀鸡用牛刀跨服务通信EventBus 又鞭长莫及。各管一段别混用。三、三步上手在 Spring Boot 里用 EventBusEventBus 的全部 API 浓缩成三步——定义事件、注册订阅、发送事件背下来就能用。它不绑 AndroidSpring Boot 后端一样能跑把依赖换成eventbus-java就行。如果你团队已经定了用 EventBus或者要和 Android 端共用一套事件模型下面这套 Spring Boot 写法直接抄至于要不要在 Spring 里上它选型建议放在第六节。第一步引入 Maven 依赖。在pom.xml里加这一段!-- 以 Maven Central 3.3.1 为准Spring Boot 2.x 注解用 javax3.x 用 jakarta --dependencygroupIdorg.greenrobot/groupIdartifactIdeventbus-java/artifactIdversion3.3.1/version/dependency第二步定义事件 写订阅者。事件就是一个普通 POJO不继承任何基类订阅者用Subscribe标注接收方法方法必须 public、有且仅有一个参数publicclassLoginEvent{privatefinalStringusername;publicLoginEvent(Stringusername){this.usernameusername;}publicStringgetUsername(){returnusername;}}ComponentpublicclassUserModule{Subscribe(threadModeThreadMode.POSTING)publicvoidonLogin(LoginEventevent){System.out.println(用户 event.getUsername() 已登录);}PostConstruct// Bean 启动时注册publicvoidinit(){EventBus.getDefault().register(this);}PreDestroy// Bean 销毁时注销publicvoiddestroy(){EventBus.getDefault().unregister(this);}}把订阅者声明成 Spring 的Component再用PostConstruct注册、PreDestroy注销register 和 unregister 就跟着 Bean 生命周期自动成对不用你手动记着去调。第三步发送事件。在任何 Service 里直接 post发布者完全不需要知道谁在监听ServicepublicclassLoginService{publicvoidlogin(Stringusername){// 业务处理……EventBus.getDefault().post(newLoginEvent(username));}}我见过太多线上 OOM根因就是register了却没unregister——EventBus 持有订阅者引用对象回收不掉。所以哪怕图省事也务必让注册和注销跟着生命周期走别只 register 不注销。下面这张时序图展示了从post到被订阅方法处理的完整链路发布者 post 后EventBus 按事件类型查订阅表再按线程模式决定同步直调还是丢进线程池。四、Subscribe 三个关键参数线程模式、粘性事件、优先级Subscribe注解的三个参数不是花架子它们决定了事件在哪个线程跑、能不能收到「早到」的事件、谁先收到。SubscribeEventBus 的订阅方法注解标注「这个方法要接收什么类型的事件」可指定线程模式、是否粘性、优先级。被注解的方法要求 public 修饰、有且仅有一个参数。粘性事件Sticky EventpostSticky发出后会被 EventBus 缓存下来的事件之后新注册的订阅者也能收到它。你可以理解成「迟到的订阅者也能拿到上一条广播」。线程模式ThreadMode决定了订阅方法在哪个线程被执行。纯 Java 后端主要用 POSTING 和 ASYNCMAIN 系列依赖 Android 主线程后端用不到ThreadMode在哪执行纯 Java 能用典型场景POSTINGpost 所在线程直接同步调用能不耗时的事件最常用ASYNC永远丢进线程池异步执行能订阅者要做耗时 IOBACKGROUNDpost 在主线程则入池在子线程则直接调能轻量后台任务MAINAndroid 主线程执行否更新 UIMAIN_ORDERED主线程按顺序非阻塞否多个 UI 更新串行粘性事件有个坑要单独说缓存的粘性事件不会自动消失重复postSticky同类型会覆盖旧的但如果你忘了removeStickyEvent换场景后新订阅者可能收到一条本不该收的「历史事件」。我的习惯是粘性事件用完立刻removeStickyEvent别让缓存兜着脏数据。优先级priority是个 int数字越大越先收到且只在同一线程模式下才有意义。五、什么场景下用 EventBusEventBus 适合「一对多、发布者不想知道订阅者是谁」的场景不适合「调用方需要返回值」或「强顺序依赖」的场景。后者本质上是在做方法调用硬套事件总线只会让链路更难追踪。几类典型场景全局状态广播登录登出、网络断连恢复——一处 post多处 UI 或模块各自响应。跨层组件通信Fragment 之间、Fragment 与 Activity、Service 与页面不再借 Activity 中转。后端模块解耦领域事件的轻量版——下单成功后通知积分、库存、推送模块各自处理下单服务不关心下游有谁。配置热更新配置中心拉到新值后 post 一个ConfigChangedEvent各模块自己刷新。可能有人会问后端真要用 EventBus 吗还是直接 SpringEventListener更香老实说纯 Spring 后端我更推荐用 Spring 自带的EventListener零额外依赖、和容器生命周期天然集成、还支持TransactionalEventListener跟着事务走。EventBus 的甜区在 Android 和非 Spring 的纯 Java 工程——没有 Spring 容器、又想要个轻量发布订阅它比手撸观察者模式省心。要是跨进程老老实实上 MQ。六、后端视角的取舍EventBus vs Guava vs Spring选哪个看你在不在 Spring 容器里、要不要跨进程、对依赖体积多敏感。方案依赖线程模型跨进程适合谁greenrobot EventBus~60k jarThreadMode 五种否Android / 非 Spring 纯 JavaSpring 里也能跑Guava EventBusGuava 整包同步或异步二选一否已用 Guava 的纯 Java 项目Spring EventListenerSpring 容器同步可配异步否任何 Spring 后端Kafka / RabbitMQ独立中间件异步是跨服务、跨进程EventBus 也不是银弹几个老问题一直都在事件类会越定义越多一个项目几十个 Event 类很常见订阅方法靠反射查找编译时索引能优化但要配 annotation processor忘了unregister就内存泄漏最麻烦的是事件流难追踪——post一条事件到底谁会响应、什么顺序得全局搜调试链路偏长。这些都是它换来解耦的代价。写法上以 Maven Central 的org.greenrobot:eventbus:3.3.12021 年最后大版本为准长期稳定。Android 新项目也有人用 Kotlin 的 Flow / Channel 替代但就「最快上手、几行解耦」而言EventBus 依然是门槛最低的方案之一。后端要不要上 EventBus我的判断就一句在 Spring 里别折腾EventListener已经够用写 Android 或纯 Java 又想轻量解耦EventBus 依然是几行代码搞定、最省心的那个。跨进程的事交给 MQ别让一条进程内总线去干它不该干的活。
郑州网站建设
网页设计
企业官网