Rain 扩展点总览
Rain 不只是提供注入、事件和任务等现成功能。它更重要的用途,是让框架开发者把新的注解、新的组件类型、新的调用协议和新的运行时能力接入同一套扫描、容器与生命周期。
本页先回答三个问题:扩展点在哪里、何时执行、应该选择哪一个。具体模块 API 仍以各模块页面为准。
生态项目还有面向下游组件作者的专门指南:
- SmartWeb 扩展开发:统一响应、用户解析、组合 Action 注解、模板与服务器适配器。
- SmartAccess 扩展开发:Access 元数据、数据库后端、事务协议与代码生成 SPI。
扩展点地图
| 扩展点 | 所在模块 | 执行阶段 | 适合解决的问题 |
|---|---|---|---|
Module | application | 类扫描前 | 提前注册框架基础设施或修改启动环境 |
ClassRegister | application | 扫描每个类时 | 建立全局类索引、收集模型元数据 |
@LoadBy + Loader | api/application | 扫描完成后、服务启动前 | 实现注解驱动或接口驱动的模块发现 |
ApplicationService | api/application | Loader 完成后 | 启动和停止线程池、服务器、连接等资源 |
@AutoBind | api/application | 扫描期间 | 把接口的实现自动绑定到容器 |
BeanFactory<T> | di/application | 扫描期间注册,取 Bean 时创建 | 接管某一类对象的创建方式 |
DataReaderFactory | di | 参数和字段读取时 | 为新的泛型容器或数据来源提供读取规则 |
ProcessProvider | controller | Controller 加载时创建过程 | 实现鉴权、审计、事务等调用过程 |
ProcessFilter | controller | Controller 加载时筛选过程 | 控制某个过程是否作用于某个 Action |
ControllerLoader | controller | Loader 阶段 | 基于 Controller 内核实现新的协议框架 |
NextTime | job | 每次计算下次执行时间时 | 实现新的任务时间策略 |
EventBus | api/event | 运行期 | 替换事件分发实现 |
ClassTransformer | classloader | 类定义前 | 修改任意待加载类的字节码 |
@EnchantBy + Enchanter | classloader | 类定义前 | 以注解声明局部字节码增强 |
@HookBy + HookRunnable | hook | 增强后方法调用时 | 实现方法级前置、后置和异常逻辑 |
这些扩展点不是同一层级。优先使用更高层的协议:能用 Loader 完成发现就不要写 ClassTransformer,能用 ProcessProvider 完成调用拦截就不要直接改 Controller 内核。
启动时序中的位置
ConfigImpl.init
└─ 合并 conf/module、conf、conf/{runMode}
ContextImpl.init
└─ 建立配置读取与 Bean 创建能力
Module.onLoad
└─ 扫描前的框架初始化
AppLoader 扫描 rain.scanPackages
├─ @AutoBind
├─ BeanFactory<T>
├─ ClassRegister.register
├─ @LoadBy 搜索
└─ ApplicationService 类型收集
Loader.load
└─ 按 priority 从小到大执行
ApplicationService.start
└─ 按 priority 从小到大执行
AppStatusEvent.AppStarted由此可以确定职责边界:
Module能在扫描前工作,但它必须通过rain.modules显式配置。ClassRegister会看见扫描范围中的每个类,不应执行网络访问或重型初始化。Loader负责把静态声明变成运行时注册,不应长期持有后台任务。ApplicationService负责资源生命周期,不应重新实现类型发现。ClassTransformer发生得更早:目标类一旦已由父加载器定义,就无法再通过当前加载过程增强。
首选扩展:Loader
Loader 是 Rain 模块最常用的发现协议。框架开发者定义一种声明方式,再让 Loader 批量处理扫描结果。
@LoadBy(CommandLoader::class)
@Target(AnnotationTarget.CLASS)
annotation class Command(val name: String)
class CommandLoader(
private val context: DiContext,
private val registry: CommandRegistry,
) : Loader {
override fun priority() = 20
override fun load(items: Collection<LoadItem>) {
items.forEach { item ->
val annotation = item.annotation as Command
val handler = context.getBean(item.clazz)
?: error("无法创建命令处理器: ${item.clazz.name}")
registry.register(annotation.name, handler)
}
}
}LoadItem 的三个核心字段含义不同:
clazz:最终被发现的业务类。target:触发@LoadBy的注解、接口、父类或类型本身。annotation:业务类上实际出现的功能注解;通过接口或父类触发时可能为空。
@LoadBy.mastBean 保留了源码中的拼写。默认值为 true,表示只接收 Rain 判断为具体 Bean 的类;扩展接口、抽象基类或注解类型本身时,需要明确评估是否设为 false。
扫描所有类:ClassRegister
当需求与某个功能注解无关,而是必须观察每个扫描类时,使用 ClassRegister:
class ModelIndex : ClassRegister {
private val models = mutableListOf<Class<*>>()
override fun register(clazz: Class<*>) {
if (clazz.isAnnotationPresent(Entity::class.java)) {
models += clazz
}
}
fun all(): List<Class<*>> = models.toList()
}然后通过模块默认配置或应用配置注册:
rain.classRegisters=com.example.framework.ModelIndex配置项会合并为数组,因此多个依赖可以各自贡献一个 Register。Register 的实例由容器创建,可以注入其他轻量服务。
扫描前初始化:Module
Module.onLoad() 是扫描前入口:
class CommandModule(
private val context: DiContext,
) : Module {
override fun onLoad() {
context.putBean(CommandRegistry::class.java, CommandRegistry())
}
}注册方式:
rain.modules=com.example.framework.CommandModule如果类型本身可以在扫描后由容器正常创建,就不要为了“模块感”额外使用 Module。它适合真正必须先于扫描完成的初始化。
资源生命周期:ApplicationService
Loader 注册完元数据后,ApplicationService 才开始启动:
class CommandServer(
private val registry: CommandRegistry,
) : ApplicationService {
override fun priority() = 50
override fun start() {
registry.open()
}
override fun stop() {
registry.close()
}
}当前实现按优先级升序启动,也按同一顺序停止,并非反向关闭。存在资源依赖时,不要假设高优先级服务会先停止;应由一个聚合服务显式管理内部资源顺序。
对象创建:AutoBind 与 BeanFactory
接口只需要实现选择时,优先用 @AutoBind:
@AutoBind
interface Serializer
@Named("json")
class JsonSerializer : Serializer目标对象不能由普通构造器创建时,使用 BeanFactory<T>。Rain 会从工厂的泛型参数识别目标类型,并建立专用 ClassContext。
Controller 扩展层级
Controller 模块提供三层扩展能力:
ProcessProvider:为现有协议增加 Before、After、Catch 过程,最常用。ProcessFilter:在加载阶段判断某个过程是否应用于 Action。ControllerLoader:实现新的路由和 Action 调用协议,适合 Web、RPC、消息消费等框架作者。
实现鉴权、限流、审计时先选择 ProcessProvider。只有调用上下文、路由树和 ActionInvoker 都不同,才继承 ControllerLoader。
字节码扩展层级
类增强也分三层:
@HookBy:业务方法级拦截,优先选择。@EnchantBy:对带功能注解的类或方法执行 ASM 修改。ClassTransformer:观察所有通过 AppClassloader 定义的候选类。
Transformer 必须在目标类加载前注册,且只能在 FullStackApplicationLauncher 的类加载边界内生效。扩展代码还要避免同时从父加载器与 AppClassloader 引用同一实现类,否则会出现“类名相同但类型不同”的转换错误。
如何选择
| 需求 | 首选方案 |
|---|---|
| 扫描自定义注解并注册处理器 | @LoadBy + Loader |
| 收集所有实体或模型 | ClassRegister |
| 启动服务器或连接池 | ApplicationService |
| 为接口发现多个实现 | @AutoBind |
| 从外部系统创建对象 | BeanFactory<T> |
| 给 Action 添加鉴权或审计 | ProcessProvider |
| 新建 RPC/消息 Controller 协议 | ControllerLoader |
| 自定义任务调度时间 | NextTime |
| 给方法添加事务式边界 | @HookBy |
| 修改类结构或生成方法 | Enchanter / ClassTransformer |
下一步可按框架模块开发实战完成一个可复用模块,并结合 Application、Controller 与类增强页面深入具体实现。