Skip to content

事件总线

事件总线是 ACE Framework 提供的轻量级进程内消息机制,用于在服务、控制器等组件之间实现解耦通信。发布方只管抛出事件对象,监听方通过 @EventListener 自动接收,双方无需互相依赖。

这一机制特别适合以下场景:

  • 用户注册后触发邮件发送、积分发放等多项后续操作
  • 订单状态变更后通知多个下游模块
  • 审计日志的异步写入

定义事件类

事件是普通的仓颉 class 实例,推荐为每类业务操作定义具名事件类。@EventListener 依赖 as 类型断言做精确匹配,具名 class 是必要条件

cangjie
package ace_example.events

// 用户注册事件
public class UserRegisteredEvent {
    public let userId: Int64
    public let email: String
    public let username: String

    public init(userId: Int64, email: String, username: String) {
        this.userId = userId
        this.email = email
        this.username = username
    }
}

// 订单创建事件
public class OrderCreatedEvent {
    public let orderId: String
    public let amount: Float64

    public init(orderId: String, amount: Float64) {
        this.orderId = orderId
        this.amount = amount
    }
}

命名建议

事件类统一以 Event 结尾,放在独立的 events 包或文件中,便于全局搜索和维护。

发布事件

在任意被容器管理的组件(@Service@Controller 等)中注入 EventBus,调用 publishEvent 发布事件。

cangjie
package ace_example.service

import ace_framework.*
import ace_example.events.*

@Service
public class UserService {
    @Inject
    private var eventBus: EventBus = EventBus()

    public func register(email: String, username: String): Int64 {
        // 业务逻辑:写库、生成 ID 等
        let userId = saveUser(email, username)

        // 发布事件,容器内所有匹配监听器将被同步调用
        eventBus.publishEvent(UserRegisteredEvent(userId, email, username))

        return userId
    }
}

订阅事件

@Service 方法上添加 @EventListener,方法唯一参数即为监听的事件类型。容器启动时自动扫描并注册所有监听器。

cangjie
package ace_example.service

import ace_framework.*
import ace_example.events.*

@Service
public class EmailService {
    // 精确匹配 UserRegisteredEvent 类型
    @EventListener
    public func onUserRegistered(event: UserRegisteredEvent): Unit {
        println("发送欢迎邮件至 ${event.email}")
        // sendWelcomeMail(event.email, event.username)
    }
}

@Service
public class PointsService {
    @EventListener
    public func onUserRegistered(event: UserRegisteredEvent): Unit {
        println("为用户 ${event.userId} 发放新人积分")
        // grantInitialPoints(event.userId, 100)
    }
}

类型精确匹配

事件派发基于 as 类型断言,子类事件不会匹配父类监听器。若需要多个事件共享处理逻辑,请在监听器内部手动处理,或分别为每个事件类型注册监听器。

完整示例:用户注册流程

下面展示从 Controller 触发、Service 发布事件,到多个监听器响应的完整链路。

cangjie
package ace_example.controller

import ace_framework.*
import ace_example.service.*

@Controller["/users"]
public class UserController {
    @Inject
    private var userService: UserService = UserService()

    @Post["/register"]
    public func register(@Body body: RegisterRequest): RegisterResponse {
        let userId = userService.register(body.email, body.username)
        return RegisterResponse(userId: userId, message: "注册成功")
    }
}
cangjie
package ace_example.service

import ace_framework.*
import ace_example.events.*

// 监听器 1:发送邮件
@Service
public class NotificationService {
    @EventListener
    public func onRegister(event: UserRegisteredEvent): Unit {
        println("[邮件] 发送欢迎邮件 → ${event.email}")
    }
}

// 监听器 2:发放积分
@Service
public class RewardService {
    @EventListener
    public func onRegister(event: UserRegisteredEvent): Unit {
        println("[积分] 用户 ${event.userId} 获得 100 积分")
    }
}

执行 POST /users/register 时,publishEvent 调用栈内将按注册顺序依次执行两个监听器,均在同一协程内同步完成。

与 @Async 组合

若监听器内的操作耗时较长(如发送邮件、写审计日志),可在监听器方法内调用标注了 @Async 的方法,将耗时逻辑切到后台协程执行,避免阻塞发布方。

cangjie
package ace_example.service

import ace_framework.*
import ace_example.events.*

@Service
public class AsyncEmailService {
    // @Async 方法在新协程中执行,立即返回
    @Async
    public func doSendMail(email: String, content: String): Unit {
        // 调用 SMTP 或第三方邮件 API(可能耗时数百毫秒)
        println("异步发送邮件至 ${email}")
    }

    @EventListener
    public func onRegister(event: UserRegisteredEvent): Unit {
        // 监听器本身同步返回,邮件发送在后台进行
        doSendMail(event.email, "欢迎加入!")
    }
}

何时用 @Async

  • 监听器操作有 I/O 等待(网络、磁盘)→ 推荐 @Async
  • 监听器操作需要感知结果或回滚事务 → 保持同步,不用 @Async

API 参考

EventBus

方法签名说明
publishEvent(event: Object): Unit发布事件,同步调用所有匹配监听器

@EventListener

属性类型说明
无参数通过方法第一个参数的类型推断监听的事件类型

监听器方法签名约束:

约束项要求
参数数量恰好 1 个,类型为具名 class
返回值Unit(返回值被忽略)
所在类必须被容器管理(@Service 等)
访问修饰符public

注意事项

  • 监听器方法内不得抛出未捕获异常,否则将中断后续监听器的执行并向发布方冒泡。建议在监听器内使用 try-catch 捕获业务异常并记录日志。
  • 事件总线仅适用于同进程内通信,跨进程或跨节点场景请使用消息队列(如 Kafka/RabbitMQ)。

错误处理最佳实践

cangjie
@Service
public class SafeEmailService {
    @EventListener
    public func onRegister(event: UserRegisteredEvent): Unit {
        try {
            sendWelcomeMail(event.email)
        } catch (e: Exception) {
            // 记录错误,不向外抛出,保证其他监听器正常执行
            println("[ERROR] 邮件发送失败: ${e.message}")
        }
    }
}

基于 Apache-2.0 许可证发布