Skip to content

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 推荐的第三方扩展分发方式

基于 Apache-2.0 许可证发布