Event 事件总线
event 模块提供同步、进程内事件总线。它用于解耦同一 Rain 应用中的模块,不是消息队列,也不负责跨进程投递、持久化或重试。
适合 Event 的场景是“一个已经发生的事实需要多个相互独立的后续响应”,例如订单创建后记录审计、更新缓存和发送通知。不适合用 Event 隐藏必须成功的核心步骤,也不适合替代跨服务消息系统。
最短使用路径是:添加依赖 → 定义 Event → 声明 @EventListener → 注入 EventBus 发布。下文按这个顺序展开。
添加依赖
implementation("com.IceCreamQAQ.Rain:event:1.0.0-DEV12")EventBus 标记了 @AutoBind,应用引入模块并完成扫描后,通常可按接口注入:
class OrderService(
private val eventBus: EventBus,
)定义事件
事件只需实现空标记接口:
import rain.api.event.Event
data class OrderCreated(
val orderId: Long,
val customerId: Long,
) : Event事件对象应当表达“已经发生的事实”,建议使用不可变数据。不要把 Event 当作可随意读写的共享 DTO,取消事件除外。
声明监听器
import rain.event.annotation.EventListener
import rain.event.annotation.SubscribeEvent
@EventListener
class OrderEventListener(
private val mailService: MailService,
) {
@SubscribeEvent
fun onCreated(event: OrderCreated) {
mailService.sendReceipt(event.orderId)
}
}要求:
- 监听器类使用
@EventListener,并位于rain.scanPackages。 - 类必须能由 Rain DI 创建。
- 监听方法使用
@SubscribeEvent。 - 监听方法必须至少有一个事件参数;当前生成器直接读取第一个参数,推荐严格只声明一个参数。
- 方法可为实例方法或静态方法。
EventListenerLoader 扫描类的公开方法,为每个订阅方法生成 EventInvoker 实现并注册到 EventBus。
发布事件
val canceled = eventBus.post(OrderCreated(order.id, order.customerId))post 在调用线程同步执行全部匹配监听器。普通事件通常返回 false;可取消事件被取消时返回 true。
因为是同步调用:
- 发布者会承担监听器执行时间。
- ThreadLocal、事务等调用线程上下文仍然存在。
- 慢 I/O 会直接拖慢发布者。
- 需要异步时,应在监听器中显式提交到协程/线程池,并自行处理生命周期和异常。
父类型分发
生成的 EventInvoker 先执行:
if (!(event instanceof ListenerParameterType)) return;所以监听父类或接口会收到所有子类型事件:
sealed interface DomainEvent : Event
data class UserCreated(val id: Long) : DomainEvent
data class UserDeleted(val id: Long) : DomainEvent
@SubscribeEvent
fun onAnyDomainEvent(event: DomainEvent) {
// 两种事件都会进入
}EventBus 的列表按权重组织,而不是按事件类型建立索引。每次发布会遍历各权重下的监听器,类型不匹配的生成 invoker 立即返回。监听器数量非常大时应关注线性遍历成本。
权重与顺序
@SubscribeEvent(weight = SubscribeEvent.Weight.HIGH)
fun validate(event: BeforePayment) = Unit普通权重执行顺序:
HIGHEST → HIGH → NORMAL → LOW → LOWEST同一权重内按照注册列表顺序执行,但业务代码不应依赖扫描带来的同权重顺序;存在因果关系时应调整权重,或合并为一个明确的协调监听器。
RECORD 权重
RECORD 是特殊阶段,总是在其他监听器之前执行。对于可取消事件,RECORD 阶段结束后 EventBus 会把 isCanceled 重置为 false,再进入普通权重。
因此 RECORD 适合旁路记录“事件曾被发布”,不适合阻止后续传播:
@SubscribeEvent(weight = Weight.RECORD)
fun record(event: CommandEvent) {
audit.append(event)
}可取消事件
实现 CancelAbleEvent,或继承 AbstractCancelAbleEvent:
class BeforeOrderSubmit(
val order: Order,
) : AbstractCancelAbleEvent()监听器调用 cancel():
@SubscribeEvent(weight = Weight.HIGH)
fun checkStock(event: BeforeOrderSubmit) {
if (!stockService.available(event.order)) event.cancel()
}普通权重阶段中,只要监听器执行后 isCanceled == true,EventBus 立即停止传播并让 post 返回 true。
val event = BeforeOrderSubmit(order)
if (eventBus.post(event)) return SubmitResult.Rejected取消不是异常,不会自动回滚已执行监听器的副作用。应把高优先级监听器设计为验证阶段,把真正副作用放在确认不会被取消之后。
监听器异常
EventBus 捕获监听器抛出的 Throwable:
- 记录错误日志。
- 发布
EventListenerRunExceptionEvent。 - 继续事件总线控制流。
@SubscribeEvent
fun onListenerFailure(event: EventListenerRunExceptionEvent) {
alertService.report(event.listenerInfo.methodFullName, event.throwable)
}为了避免无限递归,处理 EventListenerRunExceptionEvent 本身抛错时不会再次发布同类异常事件。
错误不会传回发布者
监听器异常被 EventBus 捕获,post() 不会因普通监听器失败而抛出。需要“任何监听失败都让业务失败”的强一致流程,不应使用当前 EventBus 作为调用机制,应直接调用明确的业务服务或建立自己的失败收集协议。
编程式注册
具体实现提供:
val impl = eventBus as EventBusImpl
impl.register(MyListener::class.java, listenerInstance)它扫描类的 @SubscribeEvent 方法并生成 invoker。还可以直接注册/注销 EventListenerInfo。
这些 API 属于实现类而非稳定 EventBus 接口,业务代码优先使用注解扫描。动态插件系统确实需要卸载监听器时,再封装实现细节。
Application 生命周期事件
Application 模块发布的是 rain.application.events.AppStatusEvent,注意不要与 Event 模块中历史遗留的同名 rain.event.events.AppStatusEvent 混淆。
@SubscribeEvent
fun AppStatusEvent.AppStarted.onStarted() = UnitAppStarted 在全部 ApplicationService.start() 完成后发布;AppStopping 在服务停止前发布。
EventBus 的简单实现
核心执行逻辑可以压缩为:
for (weight in orderedWeights) {
for (listener in listeners[weight]) {
listener.invoker.invoke(event)
if (event is CancelAbleEvent && event.isCanceled) return true
}
}
return falseRain 用 ASM 消除了每次事件分发时的反射 Method.invoke:启动时生成只有 instanceof + 强转 + 方法调用 的 EventInvoker。实现短、可读、运行路径明确;代价是没有异步、事务事件、泛型事件解析、监听返回值聚合和复杂错误策略。
使用建议
- 用事件解耦“事实发生后的多个独立响应”。
- 不要用事件隐藏必须成功的核心业务步骤。
- 监听器保持快速;慢操作显式异步化。
- 用不可变事件,避免监听器顺序影响数据内容。
- 可取消事件只做前置决策,不在早期监听器执行不可逆副作用。
- 为监听器异常注册统一告警监听器。