Swa*_*rma 8 java spring caching spring-mvc
我正在使用 Spring MVC 编写一个 Web 应用程序。我有一个看起来像这样的界面:
public interface SubscriptionService
{
public String getSubscriptionIDForUSer(String userID);
}
Run Code Online (Sandbox Code Playgroud)
在getSubscriptionIDForUser实际上使网络调用其他服务来获取用户的签约细节。我的业务逻辑在其逻辑中的多个位置调用此方法。因此,对于给定的 HTTP 请求,我可能会多次调用此方法。所以,我想缓存这个结果,这样就不会对同一个请求进行重复的网络调用。我查看了Spring 文档,但找不到有关如何为同一请求缓存此结果的参考。不用说,如果缓存是对相同userID.
我的要求如下:
对于一个 HTTP 请求,如果多次调用getSubscriptionIDForUser,则实际方法应该只执行一次。对于所有其他调用,应返回缓存的结果。
对于不同的 HTTP 请求,即使方法参数完全相同,我们也应该重新调用并忽略缓存命中(如果有的话)。
业务逻辑可能会从不同的线程并行执行其逻辑。因此对于同一个 HTTP 请求,有可能 Thread-1 当前正在进行getSubscriptionIDForUser方法调用,并且在方法返回之前,Thread-2 也尝试使用相同的参数调用相同的方法。如果是这样,则应使 Thread-2 等待从 Thread-1 发出的调用的返回,而不是进行另一次调用。一旦从 Thread-1 调用的方法返回,Thread-2 应该得到相同的返回值。
任何指针?
更新:我的 webapp 将部署到 VIP 后面的多个主机。我最重要的要求是请求级缓存。由于每个请求将由单个主机提供服务,因此我只需要在该主机中缓存服务调用的结果。具有相同用户 ID 的新请求不得从缓存中获取值。我已经浏览了文档,但找不到关于它是如何完成的参考。可能是我看错了地方?
nnd*_*dru 16
我想提出另一种解决方案,该解决方案比@Dmitry 提出的解决方案要小一些。CacheManager我们可以ConcurrentMapCacheManager在'spring-context'工件中使用Spring提供的,而不是实现自己的。因此,代码将如下所示(配置):
//add this code to any configuration class
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
Run Code Online (Sandbox Code Playgroud)
并可用于:
@Cacheable(cacheManager = "cacheManager", cacheNames = "default")
public SomeCachedObject getCachedObject() {
return new SomeCachedObject();
}
Run Code Online (Sandbox Code Playgroud)
我最终得到了赫尔曼在评论中建议的解决方案:
具有简单 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();
}
}
Run Code Online (Sandbox Code Playgroud)
然后将其设置为RequestScoped:
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Run Code Online (Sandbox Code Playgroud)
用法:
@Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
Run Code Online (Sandbox Code Playgroud)
更新:
一段时间后,我发现我之前的解决方案与 Spring-actuator 不兼容。CacheMetricsRegistrarConfiguration 尝试在请求范围之外初始化请求范围的缓存,这会导致异常。
这是我的替代实现:
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();
}
Run Code Online (Sandbox Code Playgroud)
}
然后注册一个 not(!) 请求范围的 bean。缓存实现将在内部获取请求范围。
@Bean
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Run Code Online (Sandbox Code Playgroud)
用法:
@Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
Run Code Online (Sandbox Code Playgroud)
你立刻就会想到EHCache ,或者你甚至可以推出自己的解决方案来将结果缓存在服务层中。这里可能有十亿种缓存选项。选择取决于几个因素,例如您是否需要超时值,或者您是否要手动清理缓存。您是否需要分布式缓存,例如您有一个分布在多个应用程序服务器之间的无状态 REST 应用程序。您需要一些坚固耐用的东西,可以在崩溃或重新启动时幸存下来。
| 归档时间: |
|
| 查看次数: |
11360 次 |
| 最近记录: |