Use EventListener causing circular dependency with repository

26 views Asked by At

I am tring to use EntryExpiredListener to do an action using another service once cache entry expired, but I couldn't add the listener to the cache if it is calling a service, and the service is calling any repositry in the system

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
└─────┘

This is my CacheListener:

@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:

@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);
    }

}

The service is only containing an injection to masterRoleRepository whick cuase the issue. Is there a way to use the EventListener as a component and call a service to do logic?

Thank you in advance!

1

There are 1 answers

0
Orçun Çolak On

If you have a config like this you can create the listener as bean

@Configuration
@EnableAsync
public class HazelcastInstanceConfiguration {

  public static final String MAP_NAME = "myMap";

  @Bean
  public Config hazelcastConfig() {
    Config config = new Config();

    // Configure the map
    MapConfig mapConfig = config.getMapConfig(MAP_NAME);
    mapConfig.setTimeToLiveSeconds(30);

    EntryListenerConfig entryListenerConfig = new EntryListenerConfig();
    entryListenerConfig.setImplementation(cacheListener());
    mapConfig.addEntryListenerConfig(entryListenerConfig);

    return config;
  }

  @Bean
  CacheListener cacheListener() {
    return new CacheListener();
  }
}

In the listener inject the service with a setter

@Slf4j
@RequiredArgsConstructor
public class CacheListener implements EntryExpiredListener<String, String> {

  private MyService myService;

  @Autowired
  public void setMyService(MyService myService) {
    this.myService = myService;
  }

  @Override
  public void entryExpired(EntryEvent<String, String> event) {
    log.info("entryEvicted. Cache: {}. Value: {}. \nEvent: {} ", 
      event.getName(), event.getOldValue(), event);
    myService.deleteFromRepository(event.getKey());
  }
}

Make the service async in order not to block the hazelcast thread

@Service
@Slf4j
public class MyService {

  @Async
  public void deleteFromRepository(String key) {
    log.info("deleteFromRepository is called for key : {}", key);
  }
}