Skip to content

@Cacheable 缓存

@Cacheable[ttlMs] 是 ACE Framework 提供的方法级缓存宏。在编译期,宏将方法体包裹进缓存查询逻辑:首次调用时执行原始方法并写入缓存,后续相同参数的调用在 TTL 内直接返回缓存值,无需执行方法体。底层由 TtlCache 驱动,对业务代码完全透明。

快速开始

在任意 @Service 方法上添加 @Cacheable[ttlMs],参数为缓存存活时间(毫秒):

cangjie
package ace.demo.service

import ace.framework.*

@Service
public class ProductService {
    @Cacheable[5000]   // 缓存 5 秒
    public func findById(id: Int64): Product {
        // 模拟数据库查询(只在缓存未命中时执行)
        println("查询数据库: id=${id}")
        return db.queryOne("SELECT * FROM products WHERE id = ?", id)
    }
}

首次调用 findById(42) 时会执行方法体并缓存结果;5 秒内再次调用 findById(42) 将直接返回缓存值,数据库不会被访问。

缓存键规则

缓存键由所有参数的 toString 拼接生成,格式为:

<包名>.<类名>.<方法名>:<arg0.toString()>:<arg1.toString()>:...
cangjie
// 以下两次调用的缓存键不同,各自独立缓存
service.search("cangjie", 1)   // 键: "...search:cangjie:1"
service.search("cangjie", 2)   // 键: "...search:cangjie:2"

WARNING

缓存键完全依赖参数的 toString 输出。若两个参数值逻辑上相同但 toString 结果不同(例如浮点精度差异、自定义类未重写 toString),会产生不同的缓存条目,导致缓存命中率下降或结果不一致。请确保参数类型有语义正确且稳定toString 实现。

TTL 配置参考

@Cacheable[ttlMs]行为
@Cacheable[0]禁用缓存,每次调用均执行方法体(等价于不加注解)
@Cacheable[1000]缓存 1 秒,适合高频查询且允许短暂不一致的场景
@Cacheable[60000]缓存 1 分钟,适合配置项、字典表等低变更数据
@Cacheable[3600000]缓存 1 小时,适合静态资源摘要、版本号等极少变更数据

多参数缓存示例

cangjie
@Service
public class SearchService {
    @Cacheable[10000]   // 10 秒缓存
    public func search(keyword: String, page: Int64, pageSize: Int64): SearchResult {
        return elasticSearch.query(keyword, page, pageSize)
    }

    @Cacheable[30000]   // 30 秒缓存
    public func getCategory(categoryId: Int64, lang: String): Category {
        return db.queryCategory(categoryId, lang)
    }
}

与 @Around 组合使用

@Cacheable 可以与其他 AOP 宏叠加,执行顺序遵循宏声明从上到下由外到内的原则:

cangjie
@Service
public class ReportService {
    @Around["timing"]    // 最外层:计时(含缓存命中的时间)
    @Cacheable[60000]    // 中间层:缓存(命中时直接返回,不进入方法体)
    @Around["auth"]      // 内层:权限校验(仅缓存未命中时执行)
    public func generateReport(reportId: String): Report {
        return heavyReportGeneration(reportId)
    }
}

TIP

@Cacheable 本质上是一个内置的 MethodInterceptor,因此与 @Around 完全兼容,可以任意组合。将 @Cacheable 放在 @Around["timing"] 内侧可以只统计缓存未命中时的耗时;放在外侧则统计包含缓存查找的总耗时。

缓存失效

目前 @Cacheable 仅支持基于 TTL 的自动失效,不支持手动主动淘汰(evict)。

  • TTL 到期后,下一次调用自动重新执行方法体并刷新缓存。
  • 若需要在数据更新后立即使缓存失效,当前推荐做法是将 TTL 设置得足够短,或者在写操作方法中设 @Cacheable[0](禁用该方法的缓存)并通过更短 TTL 的读方法控制一致性窗口。

DANGER

不要对有副作用的方法(如写数据库、发送消息、扣减库存)使用 @Cacheable。缓存命中时方法体不执行,副作用会被静默跳过,造成数据不一致。@Cacheable 仅适用于幂等的查询方法

底层实现:TtlCache

TtlCache 是 ACE Framework 内置的线程安全内存缓存,基于 HashMap + 过期时间戳实现:

  • 读取时检查时间戳,过期条目惰性删除(下次写入时清理)。
  • 每个方法实例拥有独立的 TtlCache,不同方法的缓存互不干扰。
  • 内存占用与缓存条目数线性相关;对于参数组合数量极大的方法,请设置较短的 TTL 以控制内存。

TIP

TtlCache 也可作为普通组件直接使用,适合需要手动管理缓存生命周期的场景。详见 TtlCache API 文档

基于 Apache-2.0 许可证发布