Java 给定请求的Spring缓存
我正在使用SpringMVC编写一个web应用程序。我有一个如下所示的界面:Java 给定请求的Spring缓存,java,spring,caching,spring-mvc,Java,Spring,Caching,Spring Mvc,我正在使用SpringMVC编写一个web应用程序。我有一个如下所示的界面: public interface SubscriptionService { public String getSubscriptionIDForUSer(String userID); } getSubscriptionIDForUser实际上是对另一个服务进行网络调用,以获取用户的订阅详细信息。我的业务逻辑在其逻辑中的多个位置调用此方法。因此,对于给定的HTTP请求,我可能会多次调用此方法。所以,我想缓存
public interface SubscriptionService
{
public String getSubscriptionIDForUSer(String userID);
}
getSubscriptionIDForUser
实际上是对另一个服务进行网络调用,以获取用户的订阅详细信息。我的业务逻辑在其逻辑中的多个位置调用此方法。因此,对于给定的HTTP请求,我可能会多次调用此方法。所以,我想缓存这个结果,这样就不会对同一个请求进行重复的网络调用。我查看了,但找不到如何缓存同一请求的此结果的引用。不用说,如果缓存是对相同的用户ID的新请求,则应将其视为无效
我的要求如下:
对于一个HTTP请求,如果对用户发出多个调用getSubscriptionIDForUser
,则实际方法只应执行一次。对于所有其他调用,应该返回缓存的结果
对于不同的HTTP请求,即使方法参数完全相同,我们也应该进行新调用并忽略缓存命中(如果有)
业务逻辑可以从不同的线程并行执行其逻辑。因此,对于相同的HTTP请求,线程1可能当前正在为用户调用getSubscriptionIDForUser
方法,并且在方法返回之前,线程2还尝试使用相同的参数调用相同的方法。如果是这样,那么应该让线程2等待从线程1发出的调用的返回,而不是发出另一个调用。一旦从线程1调用的方法返回,线程2应该得到相同的返回值
有什么建议吗
更新:我的webapp将部署到VIP后面的多个主机。我最重要的需求是请求级缓存。由于每个请求将由单个主机提供服务,因此我只需要在该主机中缓存服务调用的结果。具有相同用户ID的新请求不能从缓存中获取值。我已经看了这些文件,但找不到如何做的参考资料。可能是我找错地方了吗?我一下子就想到了,或者你甚至可以推出自己的解决方案,将结果缓存在服务层中。这里可能有10亿个缓存选项。选择取决于几个因素,比如是否需要超时值,或者是否要手动清理缓存。您是否需要一个分布式缓存,比如您有一个分布在多个应用服务器之间的无状态REST应用程序。您需要能够在崩溃或重新启动后生存下来的强大功能。您可以使用Spring缓存注释并创建自己的CacheManager,在请求范围内进行缓存。或者你可以用我写的:我最终得到了赫尔曼在评论中建议的解决方案:
具有简单HashMap的缓存管理器类:
public class RequestScopedCacheManager implements CacheManager {
private final Map<String, Cache> cache = new HashMap<>();
public RequestScopedCacheManager() {
System.out.println("Create");
}
@Override
public Cache getCache(String name) {
return cache.computeIfAbsent(name, this::createCache);
}
@SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
@Override
public Collection<String> getCacheNames() {
return cache.keySet();
}
public void clearCaches() {
cache.clear();
}
}
用法:
@Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
@Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
更新:
过了一会儿,我发现我以前的解决方案与弹簧执行器不兼容。CacheMetricsRegistrConfiguration正在尝试初始化请求范围之外的请求范围缓存,这将导致异常
以下是我的替代实现:
public class RequestScopedCacheManager implements CacheManager {
public RequestScopedCacheManager() {
}
@Override
public Cache getCache(String name) {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.computeIfAbsent(name, this::createCache);
}
protected Map<String, Cache> getCacheMap() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return new HashMap<>();
}
@SuppressWarnings("unchecked")
Map<String, Cache> cacheMap = (Map<String, Cache>) requestAttributes.getAttribute(getCacheMapAttributeName(), RequestAttributes.SCOPE_REQUEST);
if (cacheMap == null) {
cacheMap = new HashMap<>();
requestAttributes.setAttribute(getCacheMapAttributeName(), cacheMap, RequestAttributes.SCOPE_REQUEST);
}
return cacheMap;
}
protected String getCacheMapAttributeName() {
return this.getClass().getName();
}
@SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
@Override
public Collection<String> getCacheNames() {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.keySet();
}
public void clearCaches() {
for (Cache cache : getCacheMap().values()) {
cache.clear();
}
getCacheMap().clear();
}
用法:
@Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
@Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
我想提出另一个比@Dmitry提出的解决方案小一点的解决方案。我们可以在“Spring上下文”工件中使用Spring提供的ConcurrentMapCacheManager
,而不是实现自己的CacheManager
。因此,代码如下所示(配置):
并可用于:
@Cacheable(cacheManager = "cacheManager", cacheNames = "default")
public SomeCachedObject getCachedObject() {
return new SomeCachedObject();
}
我的webapp将部署到VIP后面的多个主机。我最重要的需求是请求级缓存。由于每个请求将由单个主机提供服务,因此我只需要在该主机中缓存服务调用的结果。具有相同用户ID的新请求不能从缓存中获取值。我已经看了这些文件,但找不到如何做的参考资料。也许我看错地方了?@SwarangaSarma你最后用了什么解决方案?我怀疑ehCache是否提供基于请求作用域的缓存。您的README.md在每个请求中都有一个清除缓存的警告。难道不可能简单地将CacheManager bean本身作为请求的作用域,这样每个请求都将获得一个新实例吗?我甚至不确定在这种情况下是否需要编写自定义实现。毕竟,您正在将对象存储在线程本地
,这正是请求范围所做的。感谢您的共享。不幸的是,它没有@nndru语法那么优雅。不过,这是使用CacheMetricsRegistrConfiguration的唯一解决方案。顺便问一下,为什么CacheMetrics无法找到cacheManager bean?由于@Scope
注释,该bean应该在这里并由spring代理。。。可能web/mvc或其他任何东西当时还没有初始化?问题是,Spring正在尝试在容器启动时初始化缓存管理器。此时Trere不是请求上下文,spring抛出异常。Hi@Dmitry。感谢您提供有关致动器的提示。在陷入困境之前是不会猜到的。顺便问一下,为什么您在更新的版本中提到不(!)注册bean,而您仍在使用@Scope(value=WebApplicationContext.Scope\u REQUEST,proxyMode=ScopedProxyMode.TARGET\u CLASS)
注释,就像以前的版本一样?我猜您忘记了修改代码,但在这种情况下无法说出正确的实现方法。一个普通的咖啡豆就足够了?嗨,莱米。你是对的。我忘了删除范围注释。我已经更正了我的答案。谢谢这是否也适用于分布式应用程序基础架构的缓存?如何使用EhCache配置缓存清除功能?有人知道吗?ScopedProxyMode。鉴于调用方不太可能使用Concur的任何特定内容,接口也应该作为代理模式工作
@Cacheable(cacheManager = "cacheManager", cacheNames = "default")
public SomeCachedObject getCachedObject() {
return new SomeCachedObject();
}