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.
Perhaps you could try something like:
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;
}
}
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()
.
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.