Component 组件开发
ACE Framework 的组件系统对标 MidwayJS Component,是框架的主要扩展单元。一个 Component 可以:
- 向洋葱中间件链贡献中间件(按 order 排序)
- 向 IoC 容器提供 Bean(供
@Inject注入) - 声明默认配置(用户未设置时自动合并)
- 声明对其他组件的依赖(保证 setup 顺序)
- 条件装配(
enabled返回 false 则整个组件跳过)
快速上手
cangjie
import ace_framework_runtime.*
@Component
public class RateLimitComponent {
public func name(): String { "ratelimit" }
public func defaults(): Array<(String, String)> {
[("ratelimit.max", "100"), ("ratelimit.windowMs", "60000")]
}
public func setup(ctx: ComponentContext): Unit {
let cfg = ctx.namespace("ratelimit")
let max = cfg.getInt("max", 100)
let windowMs = cfg.getInt("windowMs", 60000)
ctx.useMiddleware(200, rateLimitMiddleware(max, windowMs))
}
}@Component 宏自动补全 <: Component 接口声明,并生成顶层自注册语句,无需手写 AceApplication.use()。
Component 接口
cangjie
public interface Component {
func name(): String
func dependsOn(): Array<String> // 默认 []
func defaults(): Array<(String, String)> // 默认 []
func enabled(config: Config): Bool // 默认 true
func setup(ctx: ComponentContext): Unit // 默认空
func onReady(): Unit // 默认空
func onStop(): Unit // 默认空
}| 方法 | 说明 |
|---|---|
name() | 组件唯一标识,用于 dependsOn 引用和日志 |
dependsOn() | 声明依赖的其他组件名;框架按拓扑顺序调用 setup |
defaults() | 贡献默认配置键值对;键须含命名空间前缀,如 "cache.ttl" |
enabled(config) | 条件装配;返回 false 则本组件的 setup/onReady/onStop 全部跳过 |
setup(ctx) | 主配置期:注册中间件、向容器提供 Bean |
onReady() | 所有组件 setup 完毕后调用;适合连接池预热、校验等 |
onStop() | 停机时逆序调用;适合关闭连接、刷写缓冲区等 |
ComponentContext
setup 期收到的上下文,组件通过它向框架贡献能力:
cangjie
public class ComponentContext {
// 读取配置(自动加 ns. 前缀)
public func namespace(ns: String): ConfigView
// 向洋葱注册中间件(order 小者先执行)
public func useMiddleware(order: Int64, mw: Middleware): Unit
// 向 IoC 容器登记单例 Bean
public func provide(name: String, factory: () -> Any): Unit
}配置命名空间
namespace("mycomp") 返回 ConfigView,所有读取自动加 mycomp. 前缀,实现配置隔离:
cangjie
public func setup(ctx: ComponentContext): Unit {
let cfg = ctx.namespace("mycomp")
let host = cfg.getOr("host", "localhost") // 读 mycomp.host
let port = cfg.getInt("port", 6379) // 读 mycomp.port
let enabled = cfg.getBool("enabled", true) // 读 mycomp.enabled
}用户在 application.toml 中配置:
toml
[mycomp]
host = "redis.internal"
port = 6380注册中间件
cangjie
ctx.useMiddleware(100, corsMiddleware()) // order=100,比 200 先执行
ctx.useMiddleware(200, rateLimitMiddleware())中间件 order 参考值(框架内置占 0–50):
| 范围 | 建议用途 |
|---|---|
| 0–50 | 框架内置(requestId、日志、CORS、健康检查) |
| 100–199 | 安全相关(JWT、CSRF、限流) |
| 200–499 | 业务中间件 |
| 500+ | 兜底 / 路由 |
提供 Bean
cangjie
ctx.provide("RedisClient", {=> RedisClient(host, port)})其他组件或服务可通过 @Inject 自动获得:
cangjie
@Service
public class CacheService {
@Inject
var redis: RedisClient = ...
}生命周期
程序启动
↓
合并默认配置(defaults() 贡献,用户配置优先)
↓
条件过滤(enabled() 返回 false 的组件跳过)
↓
拓扑排序(dependsOn 保证依赖先 setup)
↓
顺序 setup() ← 中间件/Bean 在此注册
↓
buildApp() ← 路由、内置中间件装配
↓
顺序 onReady()
↓
端口监听(服务对外开放)
↓
收到停机信号
↓
逆序 onStop() ← 资源释放安装方式
方式一:@Component 宏(推荐)
cangjie
@Component
public class MyComponent {
public func name(): String { "my-component" }
// ...
}宏在程序启动期自动执行 registerComponent(MyComponent()),无需任何手动安装。
方式二:显式安装
cangjie
main(): Int64 {
AceApplication.use(MyComponent())
AceApplication.run()
return 0
}适合需要将构造参数从外部注入的场景(如从命令行参数读取配置)。
完整示例:Redis 缓存组件
cangjie
package myapp.components
import ace_framework_runtime.*
@Component
public class RedisCacheComponent {
var client_: ?RedisClient = None
public func name(): String { "redis-cache" }
public func defaults(): Array<(String, String)> {
[
("redis.host", "localhost"),
("redis.port", "6379"),
("redis.ttl", "300")
]
}
public func enabled(config: Config): Bool {
config.getBool("redis.enabled", true)
}
public func setup(ctx: ComponentContext): Unit {
let cfg = ctx.namespace("redis")
let host = cfg.getOr("host", "localhost")
let port = cfg.getInt("port", 6379)
let client = RedisClient(host, Int32(port))
client_ = Some(client)
// 向容器提供 Bean,供 @Inject 使用
ctx.provide("RedisClient", {=> client})
ctx.provide("CacheStore", {=> RedisCacheStore(client, cfg.getInt("ttl", 300))})
}
public func onReady(): Unit {
// 预热:验证连接
match (client_) {
case Some(c) => c.ping()
case None => ()
}
}
public func onStop(): Unit {
match (client_) {
case Some(c) => c.close()
case None => ()
}
client_ = None
}
}对应配置:
toml
# application.toml
[redis]
enabled = true
host = "redis.internal"
port = 6380
ttl = 600组件依赖
当组件 B 依赖组件 A 注册的 Bean 时,声明 dependsOn 确保 A 的 setup 先执行:
cangjie
@Component
public class MetricsComponent {
public func name(): String { "metrics" }
public func dependsOn(): Array<String> { ["redis-cache"] }
public func setup(ctx: ComponentContext): Unit {
// 此时 redis-cache 已 setup,RedisClient Bean 已可用
let store = resolveBean("CacheStore")
// ...
}
}依赖环
框架在拓扑排序时检测循环依赖,发现环时抛出 Exception("ACE Component: 依赖环涉及 'xxx'")。
打包为库
将组件打包为独立 cjpm member,只暴露 Component 实现类,用户 AceApplication.use(YourComponent()) 或 @Component 即可引入——这是 ACE 推荐的第三方扩展分发方式。