Skip to content

Controller 模块

Rain controller 是协议无关的“路由 + Action + 过程链”底座。它不知道 HTTP、JSON 或 WebSocket;SmartWeb 正是在它上面实现 Web 协议。

如果只是开发 Web API,直接阅读 SmartWeb。本页面向想理解请求链或实现 RPC、消息命令、自定义协议适配的人。

添加依赖

kotlin
implementation("com.IceCreamQAQ.Rain:controller:1.0.0-DEV12")

核心调用图

text
协议请求
  ↓ 转换
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:过程之间交换数据。

协议层通常继承它:

kotlin
class RpcActionContext(
    val request: RpcRequest,
    val response: RpcResponse,
) : ActionContext()

基于路径的协议可继承 PathActionContext 并提供 path: Array<String>

Router

基础 Router 是标记接口,具体协议决定匹配方式。Rain 的 dss 包提供动静分离路径树:

text
当前节点
├─ static: Map<String, Router>
├─ dynamic: List<DynamicRouter>
└─ action: ActionInvoker?

匹配顺序先静态子节点,再动态 Matcher,最后当前节点 Action。静态路径因此天然优先于 {variable} 动态路径。

DssControllerLoader 支持:

  • /users/list 静态段。
  • /users/{id} 命名变量。
  • /users/{id:\d+} 正则变量。
  • Controller 父类到子类的 @Path 拼接。

RootRouter

kotlin
class RootRouter<CTX, ROT, AI>(
    val router: ROT,
    val actions: List<AI>,
)

RootRouter 持有一个协议路由入口和全部 Action 列表。命名 Bean 可选择不同 root;SmartWeb 用它实现多个命名 Web Server。

ActionInvoker

kotlin
interface ActionInvoker<CTX : ActionContext> {
    val actionClass: Class<*>?
    val actionMethod: Method?
    suspend operator fun invoke(context: CTX): Boolean
}

返回 Boolean 表示当前 invoker 是否完成处理。SkipMe 会让 SimpleActionInvoker 返回 false,使上层路由可尝试其他逻辑。

SimpleActionInvoker 的实际算法:

kotlin
beforeProcesses.any { processResult(it(context)) }
actionResult(action(context))
afterProcesses.any { processResult(it(context)) }
// 任一步抛错 → catchProcesses

过程普通返回值按类型名写入 Context;Action 普通返回值写入 context.result

ProcessInvoker

kotlin
fun interface ProcessInvoker<T : ActionContext> {
    suspend operator fun invoke(context: T): Any?
}

所有本地 Before/After/Catch、自定义业务过程和 Action 最终都统一成这个最小调用接口。这是 Rain Controller 简单实现的中心:扩展期可以使用反射和元数据,运行期只有一组函数式调用器。

本地 Before / After / Catch

kotlin
@Before(weight = -100)
fun authenticate() = Unit

@After(weight = 100)
fun audit() = Unit

@Catch(error = IllegalArgumentException::class)
fun badRequest(error: Exception) = Unit

ControllerLoader 把过程放入当前 Controller;加 @Global 则放入当前 RootRouter。最终针对每个 Action 合并 root + controller 过程,并按 weight 升序排序。

完整业务用法见 SmartWeb 过程链

only / except

kotlin
@Before(only = ["create", "update@java.lang.Long"])
fun requireWrite() = Unit

支持方法名和带参数签名匹配。only 必须命中,except 命中即排除。此过滤发生在建链时。

ProcessBy

业务注解通过元注解提供 ProcessProvider:

kotlin
@ProcessBy(PermissionProvider::class)
annotation class RequirePermission(val value: String)

Provider 获得:

  • 实际业务注解。
  • Provider 类上的功能注解(Before/After/Catch)。
  • Controller Class 和实例。
  • Action Method;类级注解时可为 null。

并返回 ProcessInvoker:

kotlin
@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:

kotlin
@ProcessFilterBy(NotPublicActionFilter::class)
@RequireLogin
annotation class SecureByDefault

ProcessFilter 接收注解、Controller 和 Action 元数据并返回 Boolean。它在启动建链阶段执行,不应读取请求数据。

ControllerLoader 扩展点

这是 Rain 面向协议框架作者的核心入口。只给现有协议增加鉴权、审计或事务时,应使用上一节的 ProcessProvider;只有 Context、路由和 Action 调用模型都需要重新定义时,才扩展 ControllerLoader。

实现新协议通常继承 ControllerLoaderDssControllerLoader,完成:

  1. findRootRouter:按名称取得/创建根信息。
  2. controllerInfo:建立 Controller 路径与协议元数据。
  3. actionInfo / makeAction:识别 Action 注解。
  4. putAction:把 invoker 放入 Router。
  5. createActionInvoker:创建协议 Action 调用器。
  6. createMethodInvoker:创建参数解析与反射调用器。
  7. 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 层。

基于 Apache License 2.0 发布