@Cacheable 缓存
@Cacheable[ttlMs] 是 ACE Framework 提供的方法级缓存宏。在编译期,宏将方法体包裹进缓存查询逻辑:首次调用时执行原始方法并写入缓存,后续相同参数的调用在 TTL 内直接返回缓存值,无需执行方法体。底层由 TtlCache 驱动,对业务代码完全透明。
快速开始
在任意 @Service 方法上添加 @Cacheable[ttlMs],参数为缓存存活时间(毫秒):
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()>:...// 以下两次调用的缓存键不同,各自独立缓存
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 小时,适合静态资源摘要、版本号等极少变更数据 |
多参数缓存示例
@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 宏叠加,执行顺序遵循宏声明从上到下由外到内的原则:
@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 文档。