我也对这样做有一段时间感兴趣。很抱歉,我还没有找到任何简单的方法来做到这一点。Spring不会为你做这件事,更多的问题是spring封装了什么缓存实现能不能做到。我假设您正在使用 EhCache 实现。不幸的是,据我所知,这个功能并没有开箱即用。
根据您的问题,有多种方法可以实现类似的目标
1)使用一个永恒的缓存时间,并有一个第二类线程定期循环缓存数据刷新它。我还没有完全做到这一点,但是 Thread 类需要看起来像这样:
@Autowired
EhCacheCacheManager ehCacheCacheManager;
...
//in the infinite loop
List keys = ((Ehcache) ehCacheCacheManager.getCache("test").getNative Cache()).getKeys();
for (int i = 0; i < keys.size(); i++) {
Object o = keys.get(i);
Ehcache ehcache = (Ehcache)ehCacheCacheManager.getCache("test").getNativeCache()
Element item = (ehcache).get(o);
//get the data based on some info in the value, and if no exceptions
ehcache.put(new Element(element.getKey(), newValue));
}
- 好处是这对于 @Cacheable 调用者来说非常快,缺点是您的服务器可能会获得比必要更多的命中
2)您可以制作一个 CacheListener 来监听驱逐事件,临时存储数据。如果服务器调用失败,请使用该数据并从该方法返回。
ehcache.xml
<cacheEventListenerFactory class="caching.MyCacheEventListenerFactory"/>
</cache>
</ehcache>
工厂:import net.sf.ehcache.event.CacheEventListener;导入 net.sf.ehcache.event.CacheEventListenerFactory;导入 java.util.Properties;
public class MyCacheEventListenerFactory extends CacheEventListenerFactory {
@Override
public CacheEventListener createCacheEventListener(Properties properties) {
return new CacheListener();
}
}
伪实现 import net.sf.ehcache.CacheException; 导入 net.sf.ehcache.Ehcache;导入 net.sf.ehcache.Element;导入 net.sf.ehcache.event.CacheEventListener;
import java.util.concurrent.ConcurrentHashMap;
public class CacheListener implements CacheEventListener {
//prob bad practice to use a global static here - but its just for demo purposes
public static ConcurrentHashMap myMap = new ConcurrentHashMap();
@Override
public void notifyElementPut(Ehcache ehcache, Element element) throws CacheException {
//we can remove it since the put happens after a method return
myMap.remove(element.getKey());
}
@Override
public void notifyElementExpired(Ehcache ehcache, Element element) {
//expired item, we should store this
myMap.put(element.getKey(), element.getValue());
}
//....
}
- 这里的一个挑战是密钥不是很有用,您可能需要在返回值中存储一些关于密钥的内容,以便在服务器调用失败时能够获取它。这感觉有点骇人听闻,我还没有确定这是否完全是防弹的。它可能需要一些测试。
3)很多努力但有效:
@Cacheable("test")
public MyObject getValue(String data) {
try {
MyObject result = callServer(data);
storeResultSomewhereLikeADatabase(result);
} catch (Exception ex) {
return getStoredResult(data);
}
}
这里的优点是它可以在服务器重新启动之间工作,您可以简单地扩展它以允许集群服务器之间共享缓存。我在 12 个集群环境中有一个版本,每个集群首先检查数据库,看看是否有任何其他集群首先获得了“昂贵”的数据,然后重用它而不是调用服务器。
一个轻微的变体也是使用第二个@Cacheable 方法和@CachePut 而不是DB 来存储数据。但这将意味着内存使用量增加一倍。这可能是可以接受的,具体取决于您的结果大小。