Java Spring Boot单例服务中实现单请求内函数缓存
单请求内缓存方法调用结果的解决方案
针对你在Spring Boot项目中需要仅在单个请求内缓存getValue方法结果的需求,以下是几种可行的实现方案:
方案一:使用@RequestScope的缓存容器
利用Spring的@RequestScope创建请求级别的缓存Map,注入到单例的LogicService中(通过代理模式实现),每个请求都会拥有独立的缓存实例,跨请求不会共享数据。
步骤:
- 创建请求作用域的缓存Bean:
@Component @RequestScope(proxyMode = ScopedProxyMode.TARGET_CLASS) public class RequestScopedCache { private final Map<String, String> cache = new ConcurrentHashMap<>(); public String get(String key) { return cache.get(key); } public void put(String key, String value) { cache.put(key, value); } public boolean containsKey(String key) { return cache.containsKey(key); } }
- 修改
LogicService,注入缓存Bean并实现手动缓存逻辑:
@Service @RequiredArgsConstructor @Slf4j public class LogicService { private final RequestScopedCache requestScopedCache; public Map<String, String> getValues() { Map<String, String> result = new HashMap<>(); // 示例:处理一批可能包含重复的key List<String> keys = Arrays.asList("key1", "key2", "key1", "key3"); for (String key : keys) { result.put(key, getValue(key)); } return result; } public String getValue(String key) { // 优先读取请求级缓存 if (requestScopedCache.containsKey(key)) { log.debug("Hit request cache for key: {}", key); return requestScopedCache.get(key); } // 调用外部服务的耗时逻辑 String value = fetchFromExternalService(key); // 存入请求级缓存 requestScopedCache.put(key, value); return value; } // 模拟外部服务调用 private String fetchFromExternalService(String key) { log.debug("Fetching value for key: {}", key); return "value_" + key; } }
此方案由Spring自动管理缓存生命周期,请求结束后缓存实例会被销毁,无需手动清理。
方案二:使用ThreadLocal存储缓存
默认Servlet容器(如Tomcat)为每个请求分配独立线程,可通过ThreadLocal存储当前请求的缓存Map,注意请求结束后必须清理ThreadLocal以避免内存泄漏。
步骤:
- 在
LogicService中定义ThreadLocal缓存:
@Service @RequiredArgsConstructor @Slf4j public class LogicService { private final ThreadLocal<Map<String, String>> requestCache = ThreadLocal.withInitial(HashMap::new); public Map<String, String> getValues() { try { Map<String, String> result = new HashMap<>(); List<String> keys = Arrays.asList("key1", "key2", "key1", "key3"); for (String key : keys) { result.put(key, getValue(key)); } return result; } finally { // 请求结束后强制清理ThreadLocal requestCache.remove(); } } public String getValue(String key) { Map<String, String> cache = requestCache.get(); if (cache.containsKey(key)) { log.debug("Hit thread-local cache for key: {}", key); return cache.get(key); } String value = fetchFromExternalService(key); cache.put(key, value); return value; } private String fetchFromExternalService(String key) { log.debug("Fetching value for key: {}", key); return "value_" + key; } // 供拦截器调用的清理方法 public void clearRequestCache() { requestCache.remove(); } }
- 配置请求拦截器统一清理(可选,避免手动清理遗漏):
@Component public class RequestCacheCleanupInterceptor implements HandlerInterceptor { @Autowired private LogicService logicService; @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { logicService.clearRequestCache(); } }
- 注册拦截器:
@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private RequestCacheCleanupInterceptor requestCacheCleanupInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(requestCacheCleanupInterceptor).addPathPatterns("/logic"); } }
方案三:局部缓存传递(轻量直接方案)
在getValues方法内创建局部缓存Map,作为参数传递给getValue,完全规避单例Bean的状态共享问题。
修改LogicService:
@Service @RequiredArgsConstructor @Slf4j public class LogicService { public Map<String, String> getValues() { // 局部缓存仅在当前方法调用(单个请求)内有效 Map<String, String> localCache = new HashMap<>(); Map<String, String> result = new HashMap<>(); List<String> keys = Arrays.asList("key1", "key2", "key1", "key3"); for (String key : keys) { result.put(key, getValue(key, localCache)); } return result; } public String getValue(String key, Map<String, String> localCache) { if (localCache.containsKey(key)) { log.debug("Hit local cache for key: {}", key); return localCache.get(key); } String value = fetchFromExternalService(key); localCache.put(key, value); return value; } private String fetchFromExternalService(String key) { log.debug("Fetching value for key: {}", key); return "value_" + key; } }
此方案无需依赖Spring作用域或ThreadLocal,逻辑简单直接,适合场景单一的需求。
内容的提问来源于stack exchange,提问作者pradhyo
相关产品推荐
相关产品推荐

