Skip to content

@Around 拦截器

ACE Framework 的 AOP 拦截器基于编译期宏织入实现,无需运行时动态代理。@Around["interceptorName"] 宏在编译期将方法体包裹进 Invocation 对象,运行期由注册的 MethodInterceptor.invoke 驱动整条调用链,对业务代码零侵入。

核心原理

@Around["timing"] 装饰方法
        ↓ 编译期宏展开
方法体 → Invocation { proceed() 调用原始实现 }
        ↓ 运行期
MethodInterceptor.invoke(inv) → inv.proceed() → 返回值

多个 @Around 叠加时,宏按声明顺序由外到内嵌套包裹,形成类似洋葱的调用链。

定义拦截器

实现 MethodInterceptor 接口,重写 invoke 方法:

cangjie
package ace.demo.interceptor

import ace.framework.aop.*

public class TimingInterceptor <: MethodInterceptor {
    public func invoke(inv: Invocation): Any {
        let start = DateTime.now().toUnixTimeMilliseconds()
        let result = inv.proceed()
        let elapsed = DateTime.now().toUnixTimeMilliseconds() - start
        println("[Timing] ${inv.className}.${inv.methodName} 耗时 ${elapsed}ms")
        return result
    }
}

Invocation 字段说明

字段 / 方法类型说明
classNameString被拦截的类名
methodNameString被拦截的方法名
argsArray<Any>方法实参列表(按声明顺序)
proceed()() -> Any调用下一个拦截器或原始方法体,返回原始返回值

WARNING

proceed() 必须调用且只调用一次。漏调会导致原始方法不执行;重复调用会使原方法执行多次(对写操作有副作用)。

注册拦截器

在应用启动前(main 函数或配置初始化阶段)调用 registerInterceptor

cangjie
import ace.framework.aop.*
import ace.demo.interceptor.*

main(): Int64 {
    registerInterceptor("timing", TimingInterceptor())
    registerInterceptor("auth",   AuthInterceptor())

    let app = AceApplication.create()
    app.listen(8080)
    return 0
}

registerInterceptor 第一个参数为拦截器名称,与 @Around 的参数对应。同一名称只能注册一个实例;重复注册会覆盖旧实例。

使用 @Around

@Service@Controller 的方法上添加 @Around["name"]

cangjie
import ace.framework.*
import ace.framework.aop.*

@Service
public class OrderService {
    @Around["timing"]
    public func createOrder(userId: Int64, amount: Float64): String {
        // 业务逻辑
        return "order-${userId}-${amount}"
    }

    @Around["auth"]
    @Around["timing"]
    public func cancelOrder(orderId: String): Bool {
        // 先经过 auth 拦截器,再经过 timing 拦截器,最后执行方法体
        return true
    }
}

链式拦截顺序

多个 @Around从上到下的顺序由外到内嵌套,执行顺序如下:

auth.invoke 开始
  └─ timing.invoke 开始
       └─ 原始方法体
     timing.invoke 结束
auth.invoke 结束

完整示例:计时拦截器

以下示例展示一个完整的计时拦截器,记录方法名、参数和耗时,并在超过阈值时打印警告:

cangjie
package ace.demo.interceptor

import ace.framework.aop.*

public class TimingInterceptor <: MethodInterceptor {
    private let warnThresholdMs: Int64

    public init(warnThresholdMs!: Int64 = 200) {
        this.warnThresholdMs = warnThresholdMs
    }

    public func invoke(inv: Invocation): Any {
        let start = DateTime.now().toUnixTimeMilliseconds()

        let result = inv.proceed()

        let elapsed = DateTime.now().toUnixTimeMilliseconds() - start
        let tag = if (elapsed > warnThresholdMs) { "[SLOW]" } else { "[OK]" }
        println("${tag} ${inv.className}.${inv.methodName}(${inv.args}) -> ${elapsed}ms")

        return result
    }
}

注册时传入自定义阈值:

cangjie
registerInterceptor("timing", TimingInterceptor(warnThresholdMs: 100))

典型用途

场景说明
耗时统计记录方法执行时间,慢方法告警
事务管理proceed() 前开启事务,成功后提交,异常时回滚
权限校验读取 RequestContextHolder 中的用户信息,无权限时抛出异常
日志记录统一记录入参/出参/异常,减少业务层日志代码
重试 / 熔断捕获异常后重试或降级(也可直接用 @Retry/@CircuitBreaker

TIP

@Around@Timed@Retry@CircuitBreaker 等内置宏可同时使用。内置宏的拦截器已由框架注册,无需手动 registerInterceptor

DANGER

拦截器中不要持有请求级别的可变状态。拦截器实例全局单例,并发调用时共享同一实例,请使用 RequestContextHolder 或方法局部变量隔离状态。

基于 Apache-2.0 许可证发布