07 · AOP 与调度

AOP 织入由宏在编译期把原方法体包进 { => <原体> }() 立即调用、前后插入逻辑,保留方法签名与返回值(可 --debug-macro 审计)。被织入方法须有显式返回类型(或 Unit)。

@Timed:方法计时

测量方法真实耗时并累计统计(调用次数 / 平均 / 最大毫秒),不改签名/返回值。

@Timed
public func handle(): Result { ... }

// 读取统计:
match (aceTimedStats("handle")) {
    case Some((count, avgMs, maxMs)) => ...
    case None => ()
}

机制:织入 let __t0 = aceNowMs() 于体首、aceTimedRecord(name, aceNowMs() - __t0) 于体尾(aceNowMs 用 std.time 真实时钟,Mutex 串行化统计)。

@Cacheable[ttlMs]:方法记忆化

按参数缓存方法返回值,TTL 为毫秒;留空表示永久:

@Cacheable[60000]                      // 缓存 60 秒
public func getUser(id: Int64): UserDto { ... }

@Cacheable                              // 永久缓存(无 TTL)
public func loadConfig(): ConfigDto { ... }
  • 缓存键:由各参数 toString() 拼成(p0|p1|...),无参时键为空串。参数类型须可 toString。
  • 后端:每方法一个 TtlCache<返回类型>,惰性过期、Mutex 串行化、有界(默认上限 10000 项,超限清理)。
  • 作用域:单例 Bean 下缓存随实例存活(一般即应用生命周期)。

@Async:异步执行

把方法改为在新协程运行,返回类型自动变为 Future<原返回类型>:

@Async
public func sendMail(to: String): Bool { ... }    // 实际签名变为 (): Future<Bool>

// 调用方:
let fut = mailer.sendMail("a@b.com")
let ok = fut.get()                                 // 等待结果

@Scheduled[ms]:周期任务

每隔指定毫秒执行一次。方法必须无参:

@Service
public class Heartbeat {
    @Scheduled[5000]                    // 每 5 秒
    public func tick(): Unit {
        logger.info("heartbeat", [])
    }
}

机制:宏向所属 @Service/@Controller 登记任务;调度器为每个任务起一个协程,sleep(interval) 后从容器解析 Bean 并调用。调度在应用启动后运行。

测试时可用 runScheduledTasksOnce() 手动触发一次,scheduledTaskCount() 内省任务数。

下一章:安全。

@Retry[n]:自动重试(弹性)

方法抛异常时自动重试,最多 n 次尝试(含首次);第 n 次仍失败则重抛。被重试方法应幂等。

@Retry[3]
public func callFlaky(): Data { ... }   // 失败自动重试,3 次都失败才抛出

机制:有界循环包裹原体,每次 try 调用,失败记录异常并续试,循环结束(全失败)抛最后一次异常。

@CircuitBreaker[threshold]:熔断(弹性)

连续失败达 threshold 次后"熔断":后续调用直接抛 HttpError(503, "circuit open") 快速失败,不再触达原体;任一成功重置计数。

@CircuitBreaker[5]
public func callUpstream(): Resp { ... }  // 连续 5 次失败 → 熔断,后续 503

机制:每方法一个 CircuitBreaker 状态字段(Mutex 串行化计数)。isOpen() 为真即快速失败;否则执行原体,成功 onSuccess() 重置、异常 onFailure() 计数后重抛。

当前为简单熔断(达阈值即开、需成功调用或 reset() 恢复),暂无基于时间的半开(half-open)自动探测——后续可加。

应用事件:@EventListener + publishEvent

进程内事件总线(对标 Spring ApplicationEvent / Nest EventEmitter),用于解耦:发布方不直接依赖处理方。

public class OrderPaidEvent {          // 事件用 class(便于按类型派发)
    public let orderId: Int64
    public init(orderId: Int64) { this.orderId = orderId }
}

@Service
public class NotifyService {
    @EventListener
    public func onPaid(e: OrderPaidEvent): Unit { /* 发通知 */ }   // 方法恰好一个事件参数
}

// 发布(任意位置):
publishEvent(OrderPaidEvent(42))       // 同步派发给所有匹配类型的监听器

机制:@EventListener 把 Bean 方法登记为 (Any)->Unit 监听器(按事件类型 as 过滤后 resolveBean 调用);publishEvent(e) 顺序派发。同步执行(需异步可在监听器内 @Async/spawn)。