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

Spring Cache调用getCache返回null但缓存实例已存数据问题

问题根因

三个错误叠加导致返回null:

  • 缓存名称拼写完全不统一:
    • CacheManager初始化时指定的缓存名是optCache(字母顺序o-p-t)
    • 原Service层@Cacheable注解指定的缓存名是otpCache(字母顺序o-t-p,和OTP一次性密码的业务语义匹配)
    • Controller层读取时写的缓存名是"cache",和前两个完全不匹配
      三个位置的缓存名没有一处对齐,自然调用getCache()拿不到对应实例。
  • Spring AOP代理内部调用失效:@Cacheable是基于Spring AOP动态代理实现的,类内部方法直接调用this.buildOtpCache()时,不会走代理逻辑,注解完全不生效,根本不会往缓存里写数据。调试时看到cacheManager里的残留数据,是之前其他测试路径写入的,不是这段内部调用生成的。
  • Controller层执行顺序错误:原代码里先执行cacheManager.getCache()读缓存,之后才调用otpService.verify()触发缓存写入,读操作在写操作之前,就算逻辑正确也拿不到值。
修复方案
  1. 统一所有位置的缓存名,按业务语义统一为otpCache,修正配置类:
@EnableCaching
public class CachingConfig {
    @Bean
    public CacheManager cacheManager() {
        return new ConcurrentMapCacheManager("otpCache");
    }
}
  1. 解决内部调用AOP失效问题,二选一即可:
    • 方案一:保留@Cacheable注解,注入自身代理对象调用,避免this直连跳过AOP:
    @Service
    public class OtpService {
        @Autowired
        private OtpService self;
    
        public void verify(Request request, String authorization) {
            String memberId = parseAuthToken(authorization).getMembershipID();
            // 通过代理对象调用才会触发@Cacheable写入逻辑
            self.buildOtpCache(memberId, request.getTokenUUID(), 0);
        }
    
        @Cacheable("otpCache")
        public OTPCache buildOtpCache(String memberId, String uuid, int counter) {
            return OTPCache.builder()
                .memberId(memberId)
                .tokenUUID(uuid)
                .timestamp(LocalDateTime.now())
                .counter(counter)
                .build();
        }
    }
    
    • 方案二:用你调整后的手动写入逻辑,注意统一缓存名即可:
    public void verify(Request request, String authorization) {
        String memberId = parseAuthToken(authorization).getMembershipID();
        cacheManager.getCache("otpCache").put(memberId, buildOtpCache(memberId, request.getTokenUUID(), 0));
    }
    
    public OTPCache buildOtpCache(String memberId, String uuid, int counter) {
        return OTPCache.builder()
            .memberId(memberId)
            .tokenUUID(uuid)
            .timestamp(LocalDateTime.now())
            .counter(counter)
            .build();
    }
    
  2. 修正Controller层执行顺序和缓存名,先触发写入再读取:
@Override
public void verifyOTP(MeOTPVerifyRequest verifyOTPRequest, String authorization) {
    String memberId = parseAuthToken(authorization).getMembershipID();
    // 先执行业务逻辑写入缓存
    otpService.verify(verifyOTPRequest, authorization);
    // 用统一的缓存名读取
    OTPCache otpCache = cacheManager.getCache("otpCache").get(memberId, OTPCache.class);
    // 后续业务逻辑
}

补充说明:ConcurrentMapCacheManager默认支持懒加载缓存,即使不在构造参数里提前声明缓存名,第一次调用对应名字的@Cacheable方法或者getCache()时也会自动创建缓存,但前提是所有位置的缓存名字符串必须完全一致,大小写、字母顺序错一个字符都无法匹配。

内容的提问来源于stack exchange,提问作者Trung Nguyen

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 22:06:24