Controller 模块
Rain controller 是协议无关的“路由 + Action + 过程链”底座。它不知道 HTTP、JSON 或 WebSocket;SmartWeb 正是在它上面实现 Web 协议。
如果只是开发 Web API,直接阅读 SmartWeb。本页面向想理解请求链或实现 RPC、消息命令、自定义协议适配的人。
添加依赖
implementation("com.IceCreamQAQ.Rain:controller:1.0.0-DEV12")核心调用图
协议请求
↓ 转换
ActionContext
↓
RootRouter
↓
Router 匹配
↓
ActionInvoker
├─ Before ProcessInvoker[]
├─ Action ProcessInvoker
├─ After ProcessInvoker[]
└─ Catch ProcessInvoker[]ControllerLoader 在启动期扫描 Controller,构建 Router 树,并把每个 Action 的最终过程数组预先组合好。运行时不重新扫描注解。
ActionContext
ActionContext 保存一次 Action 调用的共享状态:
user:协议层解析的用户。runtimeError:当前异常。result:Action 或流程控制结果。- 自定义 key/value:过程之间交换数据。
协议层通常继承它:
class RpcActionContext(
val request: RpcRequest,
val response: RpcResponse,
) : ActionContext()基于路径的协议可继承 PathActionContext 并提供 path: Array<String>。
Router
基础 Router 是标记接口,具体协议决定匹配方式。Rain 的 dss 包提供动静分离路径树:
当前节点
├─ static: Map<String, Router>
├─ dynamic: List<DynamicRouter>
└─ action: ActionInvoker?匹配顺序先静态子节点,再动态 Matcher,最后当前节点 Action。静态路径因此天然优先于 {variable} 动态路径。
DssControllerLoader 支持:
/users/list静态段。/users/{id}命名变量。/users/{id:\d+}正则变量。- Controller 父类到子类的
@Path拼接。
RootRouter
class RootRouter<CTX, ROT, AI>(
val router: ROT,
val actions: List<AI>,
)RootRouter 持有一个协议路由入口和全部 Action 列表。命名 Bean 可选择不同 root;SmartWeb 用它实现多个命名 Web Server。
ActionInvoker
interface ActionInvoker<CTX : ActionContext> {
val actionClass: Class<*>?
val actionMethod: Method?
suspend operator fun invoke(context: CTX): Boolean
}返回 Boolean 表示当前 invoker 是否完成处理。SkipMe 会让 SimpleActionInvoker 返回 false,使上层路由可尝试其他逻辑。
SimpleActionInvoker 的实际算法:
beforeProcesses.any { processResult(it(context)) }
actionResult(action(context))
afterProcesses.any { processResult(it(context)) }
// 任一步抛错 → catchProcesses过程普通返回值按类型名写入 Context;Action 普通返回值写入 context.result。
ProcessInvoker
fun interface ProcessInvoker<T : ActionContext> {
suspend operator fun invoke(context: T): Any?
}所有本地 Before/After/Catch、自定义业务过程和 Action 最终都统一成这个最小调用接口。这是 Rain Controller 简单实现的中心:扩展期可以使用反射和元数据,运行期只有一组函数式调用器。
本地 Before / After / Catch
@Before(weight = -100)
fun authenticate() = Unit
@After(weight = 100)
fun audit() = Unit
@Catch(error = IllegalArgumentException::class)
fun badRequest(error: Exception) = UnitControllerLoader 把过程放入当前 Controller;加 @Global 则放入当前 RootRouter。最终针对每个 Action 合并 root + controller 过程,并按 weight 升序排序。
完整业务用法见 SmartWeb 过程链。
only / except
@Before(only = ["create", "update@java.lang.Long"])
fun requireWrite() = Unit支持方法名和带参数签名匹配。only 必须命中,except 命中即排除。此过滤发生在建链时。
ProcessBy
业务注解通过元注解提供 ProcessProvider:
@ProcessBy(PermissionProvider::class)
annotation class RequirePermission(val value: String)Provider 获得:
- 实际业务注解。
- Provider 类上的功能注解(Before/After/Catch)。
- Controller Class 和实例。
- Action Method;类级注解时可为 null。
并返回 ProcessInvoker:
@Before(weight = -50)
class PermissionProvider : ProcessProvider<MyContext> {
override fun <T> invoke(
provideAnnotation: Annotation,
functionAnnotation: Annotation,
controllerClass: Class<T>,
controllerInstance: T,
action: Method?,
) = ProcessInvoker<MyContext> { context ->
val required = (provideAnnotation as RequirePermission).value
permissionService.check(context.user, required)
}
}ControllerLoader 从 DI 获取 Provider,所以 Provider 可以使用构造器依赖。
ProcessFilterBy
Filter 决定某个过程是否挂到某 Action:
@ProcessFilterBy(NotPublicActionFilter::class)
@RequireLogin
annotation class SecureByDefaultProcessFilter 接收注解、Controller 和 Action 元数据并返回 Boolean。它在启动建链阶段执行,不应读取请求数据。
ControllerLoader 扩展点
这是 Rain 面向协议框架作者的核心入口。只给现有协议增加鉴权、审计或事务时,应使用上一节的 ProcessProvider;只有 Context、路由和 Action 调用模型都需要重新定义时,才扩展 ControllerLoader。
实现新协议通常继承 ControllerLoader 或 DssControllerLoader,完成:
findRootRouter:按名称取得/创建根信息。controllerInfo:建立 Controller 路径与协议元数据。actionInfo/makeAction:识别 Action 注解。putAction:把 invoker 放入 Router。createActionInvoker:创建协议 Action 调用器。createMethodInvoker:创建参数解析与反射调用器。postLoad:把元数据冻结为运行时 RootRouter。
继承 DssControllerLoader 时还需实现静态/动态子 Router 创建和 RouterMatcher。
参数调用器
simple 包的 SimpleKJReflectMethodInvoker 同时理解 Java Method 与 Kotlin KFunction,可处理 Kotlin suspend、可空、默认参数。协议层继承它,为每个 MethodParam 提供 attachment getter。
SmartWeb 的 WebMethodInvoker 就是在 initParam 中把 Request、PathVar、Body 等来源编译成 getter。
流程控制
ActionResult(value):抛出后中断调用,把 value 设为 Action 结果。DoNone:停止当前链并保留特殊结果。SkipMe:停止并让 ActionInvoker 返回 false。
这些是内部控制信号,不应作为普通领域返回类型传播到业务层。
Catch 语义
SimpleCatchMethodInvoker 只在 throwableType.isInstance(context.runtimeError) 时调用底层过程。多个 Catch 按 weight 尝试。
如果过程抛出 ActionResult,Invoker 把异常清空并使用结果;过程再次抛普通异常时,当前源码倾向继续抛原异常。协议层应为异常到响应的转换建立明确测试。
为什么不直接照搬 Spring MVC
Spring MVC 用 HandlerMapping、HandlerAdapter、ArgumentResolver、ReturnValueHandler、Interceptor 等多个稳定 SPI 支持巨大生态。Rain 把核心抽象压缩成 Router、ActionInvoker、ProcessInvoker 和 ControllerLoader。
优点:
- 一次调用链很容易沿源码追踪。
- 协议层可以只实现需要的部分。
- 业务过程启动期组合,运行时直接调用。
代价:
- 参数、返回值、验证等扩展 SPI 需要协议层自行设计。
- 生态和兼容性约定少。
- 错误处理、顺序和重载规则必须靠文档与测试固定。
实现新协议建议
- 先定义 Context、Router 和最小 Action 注解。
- 参数解析在启动期建立 getter,不要每次反射扫描。
- 把业务横切需求交给 ProcessProvider,而不是堆进 Router。
- 明确 root/controller/action 三层作用域。
- 为静态/动态冲突、重载、Catch、SkipMe 写端到端测试。
- 保持协议 Adapter 薄,业务服务不依赖具体 Context。
- 先阅读扩展点总览确定是否真的需要进入 ControllerLoader 层。