使用EventListener与Repository产生循环依赖的解决方案咨询
问题:缓存过期监听器依赖Spring业务服务导致循环依赖
我尝试使用EntryExpiredListener在缓存条目过期时调用其他服务执行操作,但当该监听器调用的服务依赖系统中的任意Repository时,无法将监听器添加到缓存中,出现了Spring容器的循环依赖问题。
循环依赖链
The dependencies of some of the beans in the application context form a cycle: filterChainExceptionHandler (field private org.springframework.web.servlet.HandlerExceptionResolver sb.practice.configs.security.filters.FilterChainExceptionHandler.resolver) ↓ org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration$EnableWebMvcConfiguration ↓ openEntityManagerInViewInterceptorConfigurer defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration$JpaWebConfiguration.class] ↓ openEntityManagerInViewInterceptor defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration$JpaWebConfiguration.class] ┌─────┐ | cacheManager defined in class path resource [org/springframework/boot/autoconfigure/cache/HazelcastCacheConfiguration.class] ↑ ↓ | cacheService defined in file [D:\Dev\Locals\pt-spring-boot\target\classes\sb\practice\services\impl\CacheService.class] ↑ ↓ | activeUserCacheService_Hazelcast defined in file [D:\Dev\Locals\pt-spring-boot\target\classes\sb\practice\services\impl\caches\ActiveUserCacheService_Hazelcast.class] ↑ ↓ | masterRoleRepository defined in sb.practice.repositories.masters.MasterRoleRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration ↑ ↓ | (inner bean)#28f48a7c └─────┘
现有代码
CacheListener.java
@Slf4j public class CacheListener implements EntryExpiredListener<String, String>{ public ActiveUserCacheService_Hazelcast activeUsersCache; @Override public void entryExpired(EntryEvent<String, String> event) { log.info("entryEvicted. Cache: {}. Value: {}. \nEvent: {} ", event.getName(), event.getOldValue(), event); } }
CacheService.java
@Service @RequiredArgsConstructor public class CacheService { @Value("${hazelcast.cache.cluster.nodes}") String[] clusteringNodes; private final ActiveUserCacheService_Hazelcast activeUserCacheService_Hazelcast; public <T> boolean containsKey(long key, String cacheName) { IMap<Long, T> map = hazelcastInstance().getMap(cacheName); return map.containsKey(key); } private static MapConfig mapConfig(String name, Integer timeToLive) { MapConfig mapConfig = new MapConfig(name); mapConfig.setTimeToLiveSeconds(timeToLive); // // EntryListenerConfig entryListenerConfig = new EntryListenerConfig(); // entryListenerConfig.setImplementation(new CacheListener()); // mapConfig.addEntryListenerConfig(entryListenerConfig); final EvictionConfig evictionConfig = new EvictionConfig(); evictionConfig.setEvictionPolicy(EvictionPolicy.LRU); evictionConfig.setMaxSizePolicy(MaxSizePolicy.PER_NODE); evictionConfig.setSize(500); mapConfig.setEvictionConfig(evictionConfig); return mapConfig; } @Bean HazelcastInstance hazelcastInstance() { Config config = new Config(); CacheConstants.CACHES.forEach((name, timeToLive) -> config.addMapConfig(mapConfig(name, timeToLive))); NetworkConfig network = config.getNetworkConfig(); network.setPort(5701).setPortCount(20); network.setPortAutoIncrement(true); JoinConfig join = network.getJoin(); join.getMulticastConfig().setEnabled(false); join.getTcpIpConfig().setEnabled(true); for (String clusteringNode : clusteringNodes) { join.getTcpIpConfig().addMember(clusteringNode); } return Hazelcast.newHazelcastInstance(config); } }
解决方案
可以将EventListener作为Spring组件并调用业务服务,核心是打破循环依赖链,同时确保监听器由Spring容器管理依赖注入,具体步骤如下:
1. 让CacheListener成为Spring管理的Bean
给监听器添加@Component注解,使用构造注入依赖的业务服务,避免字段注入可能带来的初始化问题:
@Component @Slf4j public class CacheListener implements EntryExpiredListener<String, String> { private final ActiveUserCacheService_Hazelcast activeUsersCache; // 构造注入,明确依赖关系 public CacheListener(ActiveUserCacheService_Hazelcast activeUsersCache) { this.activeUsersCache = activeUsersCache; } @Override public void entryExpired(EntryEvent<String, String> event) { log.info("entryEvicted. Cache: {}. Value: {}. \nEvent: {} ", event.getName(), event.getOldValue(), event); // 直接调用业务服务处理逻辑 activeUsersCache.handleExpiredUser(event.getOldValue()); } }
2. 分离Hazelcast配置与业务服务
将HazelcastInstance的Bean定义移到单独的配置类,避免CacheService同时承担配置定义和业务逻辑的职责,打破循环依赖:
@Configuration public class HazelcastConfig { @Value("${hazelcast.cache.cluster.nodes}") String[] clusteringNodes; @Bean HazelcastInstance hazelcastInstance() { Config config = new Config(); CacheConstants.CACHES.forEach((name, timeToLive) -> config.addMapConfig(mapConfig(name, timeToLive))); NetworkConfig network = config.getNetworkConfig(); network.setPort(5701).setPortCount(20); network.setPortAutoIncrement(true); JoinConfig join = network.getJoin(); join.getMulticastConfig().setEnabled(false); join.getTcpIpConfig().setEnabled(true); for (String clusteringNode : clusteringNodes) { join.getTcpIpConfig().addMember(clusteringNode); } return Hazelcast.newHazelcastInstance(config); } private static MapConfig mapConfig(String name, Integer timeToLive) { MapConfig mapConfig = new MapConfig(name); mapConfig.setTimeToLiveSeconds(timeToLive); final EvictionConfig evictionConfig = new EvictionConfig(); evictionConfig.setEvictionPolicy(EvictionPolicy.LRU); evictionConfig.setMaxSizePolicy(MaxSizePolicy.PER_NODE); evictionConfig.setSize(500); mapConfig.setEvictionConfig(evictionConfig); return mapConfig; } }
3. 重构CacheService
简化CacheService,只保留业务逻辑,依赖Spring注入的HazelcastInstance:
@Service @RequiredArgsConstructor public class CacheService { private final HazelcastInstance hazelcastInstance; private final ActiveUserCacheService_Hazelcast activeUserCacheService_Hazelcast; public <T> boolean containsKey(long key, String cacheName) { IMap<Long, T> map = hazelcastInstance.getMap(cacheName); return map.containsKey(key); } }
4. 动态注册监听器
在Spring容器初始化完成后,动态给Hazelcast的IMap注册监听器,确保所有Bean都已完成初始化:
@Component public class CacheListenerRegistrar { private final HazelcastInstance hazelcastInstance; private final CacheListener cacheListener; public CacheListenerRegistrar(HazelcastInstance hazelcastInstance, CacheListener cacheListener) { this.hazelcastInstance = hazelcastInstance; this.cacheListener = cacheListener; } @PostConstruct public void registerListeners() { // 针对目标缓存(比如ActiveUser缓存)注册监听器 IMap<String, String> activeUserMap = hazelcastInstance.getMap(CacheConstants.ACTIVE_USER_CACHE); // 第二个参数为true,表示事件中包含旧值 activeUserMap.addEntryListener(cacheListener, true); } }
原理说明
- 分离Hazelcast配置与业务服务后,
cacheManager不再依赖业务服务链,直接依赖独立的HazelcastInstanceBean,打破了循环依赖链 - 监听器作为Spring Bean,由容器管理依赖注入,能够正常获取到依赖的业务服务和Repository
- 动态注册监听器确保了Hazelcast实例和Spring业务Bean都初始化完成后再绑定,避免了初始化顺序冲突
内容的提问来源于stack exchange,提问作者Msh
相关产品推荐
相关产品推荐

