Details about spring's caching framework are here. When I was reading this link, I thought the composite cache mentioned there was one that used levels of caching based on the order given to the constructor. On reading the documentation here, I realized that it was not so; this was so that different caching solutions could be used in different methods.
With the following code, one can have a GuavaCache
as L1 as that uses plain Java objects and wouldn't incur network latency or serialization and deserialization overhead. In case of a miss in L1 one would perform a get from L2 (and put it in L1 if found there). For L2 one might use a distributed caching solution - probably a cache running on another fleet of hosts.
TwoLevelCacheManager
package hello;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.support.NoOpCacheManager;
import java.util.Collection;
public class TwoLevelCacheManager implements CacheManager {
CacheManager currentLevel;
CacheManager nextLevel;
TwoLevelCacheManager(CacheManager singleLevel) {
this(singleLevel, new NoOpCacheManager());
}
TwoLevelCacheManager(CacheManager currentLevel, CacheManager nextLevel) {
//assert both contain same cache names or dynamic creation true?
this.currentLevel = currentLevel;
this.nextLevel = nextLevel;
}
@Override
public Cache getCache(String name) {
return new TwoLevelCacheCache(name, currentLevel.getCache(name), nextLevel.getCache(name));
}
@Override
public Collection<String> getCacheNames() {
return currentLevel.getCacheNames();
}
}
TwoLevelCacheCache
package hello;
import org.springframework.cache.Cache;
public class TwoLevelCacheCache implements Cache {
private String name;
private Cache levelOneCache;
private Cache nextLevelCache;
TwoLevelCacheCache(String name, Cache cache) {
this(name, cache, new NoOpCache(name));
}
TwoLevelCacheCache(String name, Cache levelOneCache, Cache nextLevelCache) {
this.name = name;
this.levelOneCache = levelOneCache;
this.nextLevelCache = nextLevelCache;
}
@Override
public String getName() {
return name;
}
@Override
public Object getNativeCache() {
return this;
}
@Override
public ValueWrapper get(Object o) {
ValueWrapper value = levelOneCache.get(o);
if (value == null) {
value = nextLevelCache.get(o);
if (value != null) {
levelOneCache.put(o, value.get());
}
}
return value;
}
@Override
public <T> T get(Object o, Class<T> aClass) {
T value = levelOneCache.get(o, aClass);
if (value == null) {
value = nextLevelCache.get(o, aClass);
if (value != null) {
levelOneCache.put(o, value);
}
}
return value;
}
@Override
public void put(Object o, Object o1) {
levelOneCache.put(o, o1);
nextLevelCache.put(o, o1);
}
@Override
public ValueWrapper putIfAbsent(Object o, Object o1) {
//synchronize?
ValueWrapper value = get(o);
if (value == null) {
put(o, o1);
}
return value;
}
@Override
public void evict(Object o) {
levelOneCache.evict(o);
nextLevelCache.evict(o);
}
@Override
public void clear() {
levelOneCache.clear();
nextLevelCache.clear();
}
}
TwoLevelCacheCache
class name should be changed toTwoLevelCache
. We should makeget(Object 0)
andput(Object o, Object o1)
thread safe. \$\endgroup\$