spring 默认不支持 `@cacheable` 的只读模式,但可通过自定义 `cachemanager` 和 `cache` 装饰器实现——即允许微服务安全读取缓存,同时彻底禁用所有写入操作(如 `put`、`evict`),确保缓存一致性与职责隔离。
在分布式微服务架构中,常需将缓存读写职责分离:例如,服务 A 负责业务逻辑与缓存更新(写入),而服务 B 仅作为只读消费者,从同一缓存(如 Redis)读取数据,但绝不修改它。这种设计可避免缓存污染、提升系统可观测性,并强化服务边界。遗憾的是,Spring Cache 的标准注解(如 @Cacheable、@CachePut、@CacheEvict)本身不提供原生的“只读开关”;尝试通过 condition=”false” 或 unless=”true” 等表达式禁用写入,仅能阻止方法执行或跳过缓存逻辑,却无法拦截底层 Cache.put() 等实际写操作——因为这些注解作用于方法调用层面,而非缓存存储层。
真正的解决方案在于 SPI(Service Provider Interface)级定制:利用 Spring Cache 抽象的核心扩展点——CacheManager 和 Cache 接口,构建装饰器(Decorator)来拦截并静默化所有写操作。
✅ 核心实现思路:装饰器模式
- ReadOnlyCacheManager:包装原始 CacheManager(如 RedisCacheManager),重写 getCache(String name) 方法,对返回的每个 Cache 实例进一步包装为 ReadOnlyCache;
- ReadOnlyCache:实现 org.springframework.cache.Cache 接口,委托所有读操作(如 get()、getNativeCache())给底层缓存,但对写操作(put()、putIfAbsent()、evict()、clear())全部空实现或抛出 UnsupportedOperationException。
以下为精简、生产就绪的关键代码示例:
// 只读 CacheManager 装饰器
public class ReadOnlyCacheManager implements CacheManager {
private final CacheManager source;
public ReadOnlyCacheManager(CacheManager source) {
this.source = source;
}
@Override
public Cache getCache(String name) {
Cache delegate = source.getCache(name);
return delegate != null ? new ReadOnlyCache(delegate) : null;
}
@Override
public Collection getCacheNames() {
return source.getCacheNames();
}
}
// 只读 Cache 装饰器
public class ReadOnlyCache implements Cache {
private final Cache source;
public ReadOnlyCache(Cache source) {
this.source = source;
}
// ✅ 允许读取
@Override
public T get(Object key, Class type) {
return source.get(key, type);
}
@Override
public ValueWrapper get(Object key) {
return source.get(key);
}
// ❌ 禁止写入(静默丢弃)
@Override
public void put(Object key, Object value) {
// NO-OP: 明确不写入
}
@Override
public ValueWrapper putIfAbsent(Object key, Object value) {
// 返回 null 表示未插入,不改变缓存状态
return null;
}
@Override
public void evict(Object key) {
// NO-OP
}
@Override
public void clear() {
// NO-OP
}
// 其他方法(如 getName(), getNativeCache())均委托 source
@Override
public String getName() { return source.getName(); }
@Override
public Object getNativeCache() { return source.getNativeCache(); }
}
? 集成方式(推荐 Profile 控制)
为不同微服务部署启用该机制,推荐结合 Spring Profile 实现环境隔离: