Java 给定请求的Spring缓存

Java 给定请求的Spring缓存,java,spring,caching,spring-mvc,Java,Spring,Caching,Spring Mvc,我正在使用SpringMVC编写一个web应用程序。我有一个如下所示的界面: public interface SubscriptionService { public String getSubscriptionIDForUSer(String userID); } getSubscriptionIDForUser实际上是对另一个服务进行网络调用,以获取用户的订阅详细信息。我的业务逻辑在其逻辑中的多个位置调用此方法。因此,对于给定的HTTP请求,我可能会多次调用此方法。所以,我想缓存

我正在使用SpringMVC编写一个web应用程序。我有一个如下所示的界面:

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();
    }