Asynchronous Cache Update With Spring Cache Abstraction

Luke Atkinson picture Luke Atkinson · Jun 3, 2015 · Viewed 10.9k times · Source

Using Spring's caching abstraction, how can I have a cache refresh an entry asynchronously while still returning the old entry?

I am trying to use Spring's caching abstraction to create a caching system where after a relatively short "soft" timeout, cache entries are made eligible for refresh. Then, when they are queried, the cached value is returned, and an asynchronous update operation is started to refresh the entry. I would also

Guava's cache builder allows me to specify that entries in the cache should be refreshed after a certain amount of time. The reload() method of the cache loader can then be overridden with an asynchronous implementation, allowing the stale cached value to be returned until the new one is retrieved. However, spring caching seems to not use the CacheLoader of an underlying Guava cache

Is it possible to do this kind of asynchronous cache refresh using Spring's caching abstraction?

Edit to clarify: With Guava's CacheBuilder I can use refreshAfterWrite() to get the behaviour I want. e.g. from Guava Caches Explained:

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
   .maximumSize(1000)
   .refreshAfterWrite(1, TimeUnit.MINUTES)
   .build(
       new CacheLoader<Key, Graph>() {
         public Graph load(Key key) { // no checked exception
           return getGraphFromDatabase(key);
         }

         public ListenableFuture<Graph> reload(final Key key, Graph prevGraph) {
           if (neverNeedsRefresh(key)) {
             return Futures.immediateFuture(prevGraph);
           } else {
             // asynchronous!
             ListenableFutureTask<Graph> task = ListenableFutureTask.create(new Callable<Graph>() {
               public Graph call() {
                 return getGraphFromDatabase(key);
               }
             });
             executor.execute(task);
             return task;
           }
         }
       });

However, I cannot see a way to get the behaviour of refreshAfterWrite() using Spring's @Cacheable abstraction.

Answer

Matteo picture Matteo · Jun 25, 2015

Perhaps you could try something like:

  1. Configure the cache:

    @Configuration
    @EnableCaching
    public class CacheConfig {
    
        @Bean
        public CacheManager cacheManager() {
            SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
    
            GuavaCache chache= new GuavaCache("cacheKey", CacheBuilder.newBuilder().build());
    
            simpleCacheManager.setCaches(Arrays.asList(cacheKey));
    
            return simpleCacheManager;
        }
    }
    
  2. Read the value to be cached suppose a String(I use a @Service as example)

    @Service
    public class MyService{
    
        @Cacheable("cacheKey")
        public String getStringCache() {
            return doSomething();
        }
    
        @CachePut("cacheKey")
        public String refreshStringCache() {
            return doSomething();
        }
        ...
    }
    

    Both getStringCache() and refreshStringCache() invoke the same function in order to retreive the value to be cached. The controller invoke only getStringCache().

  3. Refresh the cache with a scheduled tasks doc

    @Configuration
    @EnableScheduling
    public class ScheduledTasks {
    
        @Autowired
        private MyService myService;
    
        @Scheduled(fixedDelay = 30000)
        public void IaaSStatusRefresh(){
            myService.refreshStringCache();
        }
    }
    

    In this way the scheduled task forces the cache refresh every 30s. Anyone who accessed to getStringCache() will find an updated data into the cache.