gpt
这是一个结构清晰、目标明确、已经“可用级别”的事件系统,而且很明显你是有意识地在做:
冒泡模型(
bubbles + parentBus)生命周期 / 请求期 / 中间件 / handler / 响应 / 错误的分层
同步、确定性顺序(对调试非常友好)
下面我会按你关心的 3 个点逐一深入,然后再补充一些你现在没点名、但确实存在的隐患或可改进点。我会尽量站在“这是一个要长期维护的 Web 框架核心设施”的角度,而不是学院派。
一、你点名的 3 个问题
1️⃣ transform: ((Request) -> Request)? 的覆盖问题 —— 这是一个真实 BUG
你现在的模型是:
class RequestReceived(
val request: Request,
var transform: ((Request) -> Request)? = null
) : Event(false)❌ 问题本质
多个 listener 同时存在
每个 listener 都可能写
event.transform = { ... }后注册 / 后执行的 listener 直接覆盖前一个
而且 listener 之间没有协作协议
这是一个典型的“单写槽位”设计错误。
✅ 正确的抽象方式(推荐顺序)
✅ 方案 A(最推荐):可组合的变换链
class RequestReceived(
var request: Request
) : Event(false) {
private val transforms = CopyOnWriteArrayList<(Request) -> Request>()
fun addTransform(transform: (Request) -> Request) {
transforms += transform
}
internal fun applyTransforms() {
for (t in transforms) {
request = t(request)
}
}
}框架内部在 emit 之后:
bus.emit(event)
event.applyTransforms()优点
listener 互不覆盖
顺序 = listener 执行顺序(可预测)
语义非常清晰:我在“声明”一个修改,而不是抢占所有权
👉 这和 Koa / Express 中 middleware 对 request 的协作方式是等价的
⚠️ 方案 B:返回值聚合(不推荐)
on<RequestReceived> {
return@on it.request.copy(...)
}你会立刻遇到:
多 listener 返回哪个?
合并策略复杂
Java 兼容性差
不适合事件系统
🔎 同样的问题存在于
ResponseGenerated(
var transform: ((ResponseBody) -> ResponseBody)? = null
)👉 两个地方都应该统一成“transform list”模型
2️⃣ var source: Colleen? 可空 —— 设计语义不自洽
你说得完全对:
用户在使用它时已被赋值了,不可能为空
而你现在的代码是:
var source: Colleen? = null
internal set❌ 实际问题
对用户来说是“逻辑上必不为空”
对类型系统来说却是“可能为空”
所有用户 listener 都被迫写
event.source!!
这是 Kotlin API 设计中的一个反模式。
✅ 推荐改法(强烈)
方案 A:lateinit(最干净)
lateinit var source: Colleen
internal set并在 emit 入口 强制初始化:
fun <T : Event> emit(event: T) {
if (!::event.source.isInitialized) {
event.source = owner
}
...
}语义:框架保证在 dispatch 前一定完成注入
这是 Kotlin 框架里非常常见的模式。
方案 B:构造期注入(更纯,但侵入性大)
sealed class Event(
val source: Colleen,
val bubbles: Boolean = true
)❌ 缺点:
所有事件构造都要传
source子 App 冒泡时要不要换 source?语义会变复杂
👉 不适合你现在的冒泡模型
✅ 结论
lateinit + internal set 是最符合你当前架构的解法
3️⃣ 事件是否冗余 / 是否遗漏(重点)
我会分 冗余 和 遗漏 两部分说。
3.1 ❌ 明显冗余的事件
❌ SubAppExecuting / SubAppExecuted
class SubAppExecuting(...)
class SubAppExecuted(...)从事件语义上:
SubApp 本质上是一个 特殊的 Middleware
你已经有:
MiddlewareExecuting MiddlewareExecuted
👉 这两个事件在“观察维度”上是冗余的
✅ 推荐做法
删除这两个事件,改为:
class MiddlewareNode(
...
val kind: MiddlewareKind
)
enum class MiddlewareKind {
NORMAL,
SUB_APP
}listener 想区分时:
on<MiddlewareExecuting> {
if (it.node.kind == SUB_APP) { ... }
}收益
事件类型减少
语义更正交
后续你再加
STATIC,FILTER,ERROR_HANDLER都不用加新事件
⚠️ HandlerExecuting / HandlerExecuted 是否冗余?
这个 不算冗余,但可以考虑是否合并:
class HandlerEvent(
val phase: Phase,
val ctx: Context,
val node: RouteNode,
val duration: Long? = null
)我个人倾向于 你现在的写法是 OK 的,因为:
handler 是用户最关心的阶段
duration 是高价值指标
拆成两个事件可读性更好
👉 可以保留
3.2 ⚠️ 语义上“略显重复”的事件
ExceptionThrown vs ErrorHandled
class ExceptionThrown(...)
class ErrorHandled(...)这个设计方向是对的,但现在的边界不够清晰。
建议你明确:
事件
含义
ExceptionThrown
有异常被抛出,但
尚未被处理
ErrorHandled
异常已被某个 error middleware / handler 消化
并保证:
ErrorHandled一定发生在ExceptionThrown之后如果最终未处理,可以考虑一个:
class ExceptionUnhandled(...)
否则监控/日志系统会很难判断状态。
3.3 ⚠️ 你现在“缺失但非常有价值”的事件
🚨 1️⃣ Route Matching 事件(你自己注释掉的)
你注释掉的这几行,其实是高价值事件:
// RouteMatching
// RouteMatched
// RouteNotMatched为什么值得保留?
Debug 路由
文档 / OpenAPI 生成
APM / tracing
“为什么这个请求 404 了”
👉 建议至少保留一个:
class RouteMatched(
val ctx: Context,
val node: RouteNode
) : Event()🚨 2️⃣ RequestCompleted(最终态)
你现在最后一个是:
ResponseSent但它缺少:
status code
是否异常结束
总耗时(从 RequestReceived → ResponseSent)
👉 建议补一个 终态事件:
class RequestCompleted(
val ctx: Context,
val status: Int,
val duration: Long,
val exception: Exception? = null
) : Event(false)这是:
日志
metrics
tracing
audit
最核心的一个事件
二、你没点名,但必须说的几个“隐患级”问题
⚠️ 1️⃣ 事件类型匹配过于“严格”
listeners[event::class.java]这意味着:
on<Event> { ... } // ❌ 永远收不到是否这是你“刻意设计的”?如果不是,你可能需要:
支持父类监听
或提供
onAny {}
否则事件系统扩展性会被封死
⚠️ 2️⃣ 冒泡 + 可变 Event = 共享状态风险
你现在是:
同一个
Event实例在 parent / child bus 间共享
listener 可以修改 event
这本身不是 bug,但你需要明确这是设计选择:
“Event 是可变的、冒泡时是共享的”
否则将来你自己都会踩坑。
⚠️ 3️⃣ EventListener 未包含 priority(可能是刻意的)
你现在是 注册顺序即执行顺序。
这是 OK 的,但你以后几乎一定会想要:
logging 在前
metrics 在后
auth 在 route 前
建议你至少 留一个扩展位:
class EventListener<T : Event>(
val eventType: Class<out Event>,
val order: Int = 0,
val handler: Consumer<T>,
)哪怕现在不用。
三、总结(重点结论)
✅ 你现在设计中“确定有问题”的
transform: ((Request) -> Request)?→ 必须改为可组合source: Colleen?→ 应该是 lateinit / 非空SubAppExecuting / Executed→ 冗余,建议删除
⚠️ “方向对,但边界要收紧”的
Exception 相关事件语义
Route 相关事件是否保留
是否支持父类监听
Event 可变性是否写进设计文档
🧠 我的整体评价
这是一个已经达到「框架级设计」而不是「玩具级」的事件系统
问题集中在 API 可组合性 & 语义精炼度,而不是方向错误。
如果你愿意,下一步我可以直接帮你:
重构一版精简后的 Event 列表
或 把 Request / Response 事件抽象成一个统一的 PipelineEvent
或 对标 Koa / Spring / Vert.x 给你一个“最小但强”的事件集
你现在这个阶段,已经非常值得“精雕细琢”了。