@CacheEvict无法清除Hazelcast集群缓存,求排查问题原因
核心问题总结
当前代码存在三个关键问题,直接导致@CacheEvict注解完全失效:
1. Spring Cache AOP 内部调用不生效
removeFromCache方法直接调用了同一类中的evictFromCache方法,而Spring缓存注解(包括@CacheEvict)基于动态代理实现,同一类内部的方法调用不会触发AOP代理逻辑,因此@CacheEvict注解根本没有被执行。
2. 未配置Spring Cache与Hazelcast的集成桥接
虽然创建了HazelcastInstance客户端,但没有配置HazelcastCacheManager作为Spring Cache的实现载体。启用@EnableCaching后,Spring会默认使用简单内存缓存,和你的Hazelcast集群完全无关,所以@CacheEvict操作的是独立的缓存空间,自然无法清除Hazelcast内的数据。
3. HazelcastClient类存在变量名错误
HazelcastClient组件中注入的变量是myHazelcastInstance,但getCacheMap()方法里调用的是未定义的authHazelcast.getMap("blah-data")——这会导致空指针异常,说明你实际运行的代码存在笔误,也会影响缓存实例的正常获取。
修复步骤
步骤1:修复HazelcastClient的变量名错误
@Component @Slf4j public class HazelcastClient { @Autowired private HazelcastInstance myHazelcastInstance; public IMap<String, String> getCacheMap() { // 修正变量名,与注入的实例保持一致 return myHazelcastInstance.getMap("blah-data"); } }
步骤2:配置HazelcastCacheManager,关联Spring Cache与Hazelcast集群
在CacheConfiguration类中添加CacheManager的Bean定义:
@Configuration @RequiredArgsConstructor @Slf4j @EnableAsync @EnableCaching public class CacheConfiguration { @Bean public ClientConfig clientConfig() throws IOException { ClientConfig clientConfig = new XmlClientConfigBuilder("<the XML file specified above>").build(); return clientConfig; } @Bean(name = "myHazelcastInstance") public HazelcastInstance hazelcastInstance(ClientConfig clientConfig) { return HazelcastClient.newHazelcastClient(clientConfig); } // 新增:配置Spring Cache使用Hazelcast实现的CacheManager @Bean public CacheManager cacheManager(HazelcastInstance myHazelcastInstance) { return new HazelcastCacheManager(myHazelcastInstance); } }
步骤3:解决内部调用不触发AOP的问题
有两种可选方案:
方案A:抽离缓存操作到独立组件(推荐)
创建专门的缓存操作组件,避免同一类内部调用:
@Component @Slf4j public class CacheOperationService { @CacheEvict(value = "blah-data", key = "#p0") public void evictFromCache(final String key) { log.warn("Evicted key: {} from cache", key); } }
在MyServiceImpl中注入该组件,替换内部调用:
@RequiredArgsConstructor @Service @Slf4j public class MyServiceImpl { private final HazelcastClient hazelcastClient; private final CacheOperationService cacheOperationService; // ... 其余方法保持不变 ... @Override public void removeFromCache(final String key) { retrieveMap().remove(key); // 调用外部组件方法,触发AOP代理执行@CacheEvict cacheOperationService.evictFromCache(key); log.debug("Removed key: {} from cache", key); } // 删除原类中的evictFromCache方法 }
方案B:通过ApplicationContext获取自身代理对象(不推荐,耦合度高)
如果不想抽离组件,可注入ApplicationContext获取自身代理对象:
@RequiredArgsConstructor @Service @Slf4j public class MyServiceImpl implements ApplicationContextAware { private final HazelcastClient hazelcastClient; private MyServiceImpl selfProxy; @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { // 获取自身的代理对象 selfProxy = applicationContext.getBean(MyServiceImpl.class); } // ... 其余方法保持不变 ... @Override public void removeFromCache(final String key) { retrieveMap().remove(key); // 通过代理对象调用,触发AOP执行@CacheEvict selfProxy.evictFromCache(key); log.debug("Removed key: {} from cache", key); } @CacheEvict(value = "blah-data", key = "#p0") public void evictFromCache(final String key) { log.warn("Evicted key: {} from cache", key); } }
步骤4:(可选)统一缓存操作方式
建议要么全程使用Spring Cache注解(@Cacheable、@CacheEvict等),要么全程使用Hazelcast IMap手动操作,避免混合使用导致逻辑混乱。例如用Spring Cache改写添加和获取方法:
@Service @Slf4j public class MyServiceImpl { private final CacheOperationService cacheOperationService; public MyServiceImpl(CacheOperationService cacheOperationService) { this.cacheOperationService = cacheOperationService; } @CachePut(value = "blah-data", key = "#key") public String addToCache(final String key, final String value) { log.debug("Added key: {} value: {} to cache", key, value); return value; } @Cacheable(value = "blah-data", key = "#key") public Object getFromCache(final String key) { throw new RuntimeException("No value found for key: " + key); } public void removeFromCache(final String key) { cacheOperationService.evictFromCache(key); log.debug("Removed key: {} from cache", key); } }
内容的提问来源于stack exchange,提问作者Space Cadet

