如何在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
相关产品推荐
相关产品推荐

