You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Google Guava Cache中正确存储嵌套Map结构

代码问题排查

你的缓存思路本身没问题,Suppliers.memoizeWithExpiration不限制缓存对象类型,嵌套Map完全可以正常存储,跑不起来是几个实现细节错误导致的:

  • 异常签名不匹配:Guava的Supplier.get()不允许抛出受检异常,你写的getEmployeeDetails()声明了MessagingException、IOException两个受检异常,直接在重写的get方法里调用会直接编译不通过。
  • 构造方法定义错误:Java规定构造方法名必须和类名完全一致,你写的employeeCache首字母小写,和类名不匹配,类根本无法正常实例化。
  • 存在缓存污染风险:当前逻辑直接把业务层查到的Map引用返回出去,要是外部代码拿到Map之后修改了里面的内容,缓存里的数据会直接被篡改,因为memoize系列方法存的是对象引用,不会自动生成对象副本。
  • 初始化顺序有隐患:cachedMap直接作为成员变量初始化,虽然Supplier是懒加载暂时不会触发空指针,但后续调整逻辑时如果不小心在构造方法赋值前触发加载,直接会报employeeService空指针,把缓存初始化挪到构造方法里更稳妥。
修正后可运行代码
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.mail.MessagingException;

public class EmployeeCache {
    private final EmployeeService employeeService;
    private final Supplier<Map<String, Map<String, List<String>>>> cachedMap;

    public EmployeeCache(EmployeeService employeeService) {
        this.employeeService = employeeService;
        // 构造方法内初始化缓存,保证employeeService已完成赋值
        this.cachedMap = Suppliers.memoizeWithExpiration(
                () -> {
                    try {
                        Map<String, Map<String, List<String>>> rawData = getEmployeeDetails();
                        // 外层做不可变包装,基础场景下防止缓存被篡改
                        return Collections.unmodifiableMap(rawData);
                    } catch (MessagingException | IOException e) {
                        // 受检异常包装为非受检异常抛出,符合Supplier接口签名要求
                        throw new IllegalStateException("加载员工缓存数据失败", e);
                        // 需要降级的话可以替换为返回空默认值:return Collections.emptyMap();
                    }
                }, 10, TimeUnit.MINUTES
        );
    }

    // 对外统一的缓存查询入口
    public Map<String, Map<String, List<String>>> getCachedEmployeeDetails() {
        return cachedMap.get();
    }

    private Map<String, Map<String, List<String>>> getEmployeeDetails() throws MessagingException, IOException {
        List<String> testEmpList = new ArrayList<>(Arrays.asList("Emp3245630","Emp3269682","Emp693526","Emp0800933","Emp1817416"));
        return employeeService.getEmployeeDetails(testEmpList);
    }
}

补充:如果业务代码可能修改返回的内层Map、List元素,建议把整个嵌套结构逐层转成不可变对象,比如用Guava自带的ImmutableMap、ImmutableList做递归转换,彻底堵死缓存被篡改的可能。

使用说明
  • 缓存默认懒加载:第一次调用getCachedEmployeeDetails()时才会实际调用employeeService查询数据,之后10分钟内的所有请求都会直接返回缓存结果,过期后下次请求会自动重新加载新数据。
  • 嵌套结构不需要特殊适配:Guava的memoize工具根本不关心存储对象的内部结构,只要是合法Java对象都能正常缓存,不需要为嵌套Map做额外配置。
  • 异常逻辑可以按需调整:如果不希望缓存加载失败时抛出异常打断主业务流程,可以在catch块中返回空Map之类的默认值做降级处理。

内容的提问来源于stack exchange,提问作者saravanan G

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 16:18:20