10 · 组件扩展与封装
这是 ACE 可扩展性的核心章节。ACE 的扩展单元是 Component(对标 MidwayJS Component / Spring Starter)。
没有独立的
@Plugin机制——所有可插拔扩展统一用@Component。OpenAPI 等内置能力的「自动生成」也不走 plugin,而是宏在编译期把元数据自注册、启动期零反射生成。
中间件扩展
最轻量的扩展是中间件。类型定义在 ace-web:
public type Middleware = (Context, Next) -> Unit
public type Next = () -> Unit
@Middleware[order]:声明式注册中间件
标注「返回 Middleware 的无参函数」,按 order 升序插入洋葱(小者先执行):
@Middleware[100]
public func requestTimer(): Middleware {
return {ctx, next =>
let start = nowMs()
next()
ctx.setHeader("X-Elapsed-Ms", "${nowMs() - start}")
}
}
装配顺序(buildApp):错误处理 → requestLog → cors → health → OpenAPI/docs → 用户 @Middleware(按 order) → rangeHandler → 路由。即用户中间件排在内置链之后、路由之前。
@Catch:异常过滤器
把特定异常类型映射为定制响应(区别于洋葱中间件)。处理函数必须恰好 2 参 (ctx: Context, e: 某异常类型):
@Catch
public func onTaskNotFound(ctx: Context, e: TaskNotFoundError): Unit {
ctx.status = 404
ctx.json("{\"error\":\"task not found\",\"code\":\"TASK_NOT_FOUND\"}")
}
@Catch 过滤器优先于默认 jsonErrorHandler;按异常类型匹配,首个命中者处理。配套 @Exception 宏免写自定义异常的样板:
@Exception
public class TaskNotFoundError { // 自动 <: Exception + 标准构造器
// 可加自定义字段
}
Component 模型
Component 用于封装需要配置、生命周期、依赖编排或 Bean 注入的可复用能力。
Component 接口
public interface Component {
func name(): String // 必须实现:组件唯一名
func dependsOn(): Array<String> // 默认 []:依赖的组件名,其 setup 先于本组件
func defaults(): Array<(String, String)> // 默认 []:默认配置(键含命名空间),仅用户未设时合并
func enabled(config: Config): Bool // 默认 true:条件装配,false 则整个跳过
func setup(ctx: ComponentContext): Unit // 默认空:向应用贡献能力
func onReady(): Unit // 默认空:服务起监听前
func onStop(): Unit // 默认空:停机时(逆序)
}
ComponentContext:贡献能力的入口
setup(ctx) 里通过 ctx 向应用贡献中间件或 Bean,并读取命名空间配置:
public class ComponentContext {
public let config: Config
public func namespace(ns: String): ConfigView // 带 ns. 前缀的配置视图(隔离)
public func useMiddleware(order: Int64, mw: Middleware): Unit // 注册中间件
public func provide(name: String, factory: () -> Any): Unit // 注册单例 Bean
}
ConfigView(namespace 返回):get/getOr/getInt/getBool/has,键自动加前缀。
@Component:自注册
@Component 标注类,生成顶层 let __ace_component_X = registerComponent(X())——包被 import 时执行完成自注册。类须有无参 init()、至少实现 name():
package myapp.component
import aceboot::framework.*
import aceboot::framework_macros.*
@Component
public class GreetingComponent {
public func name(): String { "greeting" }
public func dependsOn(): Array<String> { [] } // 可选
public func defaults(): Array<(String, String)> { // 可选,键含命名空间
[("greeting.enabled", "true"), ("greeting.text", "hi")]
}
public func enabled(config: Config): Bool { // 可选,条件装配
config.getBool("greeting.enabled", true)
}
public func setup(ctx: ComponentContext): Unit { // 贡献能力
let cfg = ctx.namespace("greeting")
let text = cfg.getOr("text", "hi")
ctx.useMiddleware(50, {c, next => c.setHeader("X-Greeting", text); next()})
ctx.provide("GreetingService", {=> GreetingService(text) as Any})
}
public func onReady(): Unit {} // 可选
public func onStop(): Unit {} // 可选
}
自注册靠 import 触发。应用
main必须 import 组件所在包(如import myapp.component.*),否则组件不装配。也可显式AceApplication.use(GreetingComponent())(须在run之前)。
生命周期与装配顺序
loadProfiles → setupComponents → buildApp → readyComponents → serve → (停机) stopComponents + @PreDestroy
setupComponents 内 resolveComponentOrder 严格三步:
- 合并默认配置:遍历每个组件
defaults(),仅在用户未设置时填入(不覆盖); - enabled 过滤:
enabled(config)为 false 的剔除(此时已能读到 defaults 贡献的开关); - 拓扑排序:按
dependsOnDFS,依赖先 setup;检测到依赖环抛异常;依赖未启用/不存在则静默忽略。
onStop 按装配逆序调用,try/catch 包裹(单个异常不影响其余)。
内置组件如何写的(范例)
ACE 自带能力分两类,是学习封装的最佳范例:
真·Component(可插拔,import 即装配)
RateLimitComponent(ace-security)—— 标准范例:defaults()给[ratelimit]默认、enabled(config)条件装配、setup里ctx.useMiddleware(20, rateLimit(...))。**「贡献中间件」**路径。OrmComponent(ace-orm)——setup里从ctx.namespace("datasource")读配置(缺必填则 fail-fast 抛异常)、setGlobalDataSource、遍历实体仓储createTable()并ctx.provide(name, factory)注册为可注入 Bean。**「贡献 Bean + 依赖编排」**路径(SeedComponent用dependsOn ["orm"]保证建表后再种子数据)。
不是 Component 的内置能力
- 安全头/CSRF/JWT 是裸
Middleware工厂(securityHeaders()/csrf()/jwtAuth()),由应用用@Middleware自行接入; - CORS/请求日志/健康检查/OpenAPI 直接在
buildApp内置接入。
封装能力的复杂度梯度:简单横切 → 裸 Middleware 工厂;需配置/条件装配 → Component(贡献中间件);需 Bean 注入 + 依赖编排 → Component(贡献 Bean)。
把能力封装成可复用 module
完整步骤(对照 ace-orm/ace-security 的布局):
- 建 module:独立目录 +
cjpm.toml,output-type = "static",包名如package ace_foo。在根cjpm.toml的[workspace].members登记。 - 写能力本体:裸函数 / 中间件工厂 / Bean 类(如
fooMiddleware(): Middleware或class FooService)。 - 写 Component 封装:新文件
foo_component.cj,@Component public class FooComponent,实现name(),按需defaults()/enabled/dependsOn/setup。 - 在 setup 中贡献能力:用
ctx.namespace("foo")读配置;ctx.useMiddleware接中间件或ctx.provide注 Bean;缺必填配置throwfail-fast。 - 应用接入:应用
cjpm.toml依赖该 module,main里import ace_foo.*(触发@Component自注册),在application-{env}.toml配[foo]段。无需手写App().use(...)。
// ace-foo/src/foo_component.cj
package ace_foo
import aceboot::framework.*
import aceboot::framework_macros.*
@Component
public class FooComponent {
public func name(): String { "foo" }
public func defaults(): Array<(String, String)> {
[("foo.enabled", "true"), ("foo.size", "100")]
}
public func enabled(config: Config): Bool { config.getBool("foo.enabled", true) }
public func setup(ctx: ComponentContext): Unit {
let size = ctx.namespace("foo").getInt("size", 100)
ctx.useMiddleware(20, fooMiddleware(size))
// 或 ctx.provide("FooBean", {=> FooBean(size) as Any})
}
}
可观测
ace-observability 提供进程内指标与结构化日志(无 stdx)。
指标
public let metrics = Metrics() // 全局实例
metrics.requestCount(): Int64
metrics.errorCount(): Int64 // status >= 500 计入
metrics.avgLatencyMs(): Int64
metrics.errorRate(): Float64
接入用 metricsMiddleware()(后置测真实延迟并 record)+ metricsEndpoint("/metrics")(暴露 Prometheus 文本:ace_requests_total/ace_errors_total/ace_request_latency_ms_avg),经 @Middleware 或 App().use(...) 注册。指标经全局 metrics 实例读取(进程内),无 Prometheus scrape 端点。控制面 ace-autopilot 即读它驱动调参。
请求追踪 + 结构化日志
requestId(): Middleware // 生成/透传 X-Request-Id,写 ctx.state 与响应头
structuredRequestLog(): Middleware // 后置输出一行 JSON:{requestId,method,path,status,latencyMs}
logger.info("msg", [("k", "v")]) // 全局结构化日志器:info/warn/error,输出一行 JSON
下一章:智能体运行时。
分布式追踪(W3C Trace Context)
tracingMiddleware()(ace-observability)解析入站 traceparent 沿用其 trace-id、否则新建,每请求新建 span-id,写入 ctx.state 并回写 traceparent 响应头;currentTraceId(ctx) 读取。跨服务传播:调用方读 currentTraceId 并随出站请求带上 traceparent 头。
@Middleware[0] public func tracingMw(): Middleware { tracingMiddleware() }
// 业务: currentTraceId(ctx) → 写日志 / 透传下游