Spring 同一个类里调用 @CacheEvict,为什么缓存没清掉?
开篇
清缓存的方法加了 @CacheEvict,日志也打印了,缓存却还在。方法确实执行了,但调用可能没有经过 Spring 代理。下面看同类调用如何绕过注解,以及已有代码怎样回到代理入口。
环境:JDK 21、Spring Boot 3、Spring Framework 6。SpringContextUtil 是应用自有工具;缓存行为使用本地 ConcurrentMapCacheManager 验证,不涉及 Redis 或跨实例广播。
坑点
坑点一:方法执行了,就以为缓存拦截器也执行了
下面这个真实方法没有手动操作缓存,它依赖注解清理指定 key:
/**
* 清空所有需要验证的资源权限标识符缓存
*/
@CacheEvict(cacheNames = {"local"}, key = "'needAuthResPathList'")
public void clearNeedAuthResPathListCache() {
log.info("clearNeedAuthResPathListCache success");
}
直接使用 this.clearNeedAuthResPathListCache() 时,调用的是目标对象自身,不会重新走代理上的缓存拦截器。因此日志出现,只能证明方法体执行过,不能证明注解生效。
正确写法
避坑一:已有同类调用通过当前代理发起
在代理已经启用并暴露的前提下,调用位置使用下面两行:
SysResService self = SpringContextUtil.currentProxy(SysResService.class);
self.clearNeedAuthResPathListCache();
获取当前代理的工具方法很短:
/**
* 获取容器中当前类的代理对象
*
* @throws IllegalStateException - 如果当前类没有代理对象抛处异常
*/
public static <T> T currentProxy(Class<T> cls) {
paramNotNull(cls, "cls");
return cls.cast(AopContext.currentProxy());
}
它取的是当前调用线程暴露的代理,不是按类型从容器随便找一个 Bean。配置里必须启用 exposeProxy:
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
这还不够,缓存注解机制本身也要启用,并提供匹配的 CacheManager。当前方法调用也必须是从代理入口进来的;new 一个对象再执行,仍然没有代理上下文。
用一个本地模型看出差别
下面的 Probe 是验证调用路径的教学模型,不替代上面的业务类。两个入口都调用同一个 clear 方法,只有调用对象不同:
public static class Probe {
public void clearDirectly() {
this.clear();
}
public void clearThroughProxy() {
SpringContextUtil.currentProxy(Probe.class).clear();
}
@CacheEvict(cacheNames = "demo", key = "'key'")
public void clear() {}
}
将它注册到启用缓存与 exposeProxy 的 Spring 容器,取得代理 Bean 后再调用。下方 probe 和 manager 均来自该容器,manager 使用 ConcurrentMapCacheManager:
Cache cache = manager.getCache("demo");
cache.put("key", "value");
probe.clearDirectly();
System.out.println(cache.get("key") != null);
// true
probe.clearThroughProxy();
System.out.println(cache.get("key") == null);
// true
这个对照验证的是代理是否进入缓存拦截器,不代表真实资源权限更新、事务或集群缓存同步已完成。
新代码更适合怎样组织
如果可以调整职责,把需要缓存增强的方法放到独立 Bean,由调用方注入后调用,通常比每次获取当前代理更清楚。Spring 官方也提示,AopContext 会让业务代码与代理机制耦合,优先考虑重构调用关系。Spring AOP 代理说明
已有同类调用不能马上拆开时,上面的显式代理调用可以使用,但要守住边界:
- 不把它放到没有代理入口的初始化调用或新线程里。
- CGLIB 不能增强 final 方法,private 方法也不能用这条路径获得预期拦截。
- @CacheEvict 默认在方法成功后执行;它清理指定 key,不会自动删除整个缓存区域。
- 本地清理成功,不等于其他实例也已清理;跨实例通知是另一段链路。
总结
先确认调用经过代理,再检查 key 和缓存管理器。方法日志、业务成功与缓存拦截成功是三个不同的观察点;同类调用时尤其不能混为一谈。
如果这篇文章对您有用,欢迎关注。后续会继续拆解 Java 后端的实用代码,讲清写法、原理和使用时要注意的细节。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/MetaLite/article/details/169250988




