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

SpringBoot本地缓存问题:@Cacheable标注内部方法未生效

问题原因与解决方案

你的实现确实存在问题,核心原因是Spring的缓存注解(包括@Cacheable)依赖动态代理实现,同一个类内部的方法直接调用会绕开代理,导致缓存逻辑完全不触发。

为什么会这样?

  • Spring的缓存、事务等AOP功能,都是通过给Bean生成动态代理类实现的。当外部调用被注解标记的方法时,请求会先经过代理类,代理类会先处理缓存逻辑(比如检查缓存是否存在,不存在再调用实际方法);
  • 但如果是同一个类里的方法互相调用(比如你的retrieve()直接调用getFromDatabase()),这是直接调用原对象的方法,完全没走代理流程,所以@Cacheable注解根本没被执行,每次都会打印"Retrieving from database"。
  • 而把@Cacheable加在@PostMapping修饰的retrieve()上时,外部请求是通过Spring MVC的代理调用这个方法,所以缓存逻辑能正常生效。

解决方案

方案1:抽离缓存方法到独立Service类(推荐)

这是Spring项目的标准分层做法,把需要缓存的业务逻辑放到单独的Service层,控制器通过依赖注入调用Service方法,自然走代理流程:

// 新建DataService类
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
public class DataService {
    @Cacheable(value = "databaseCache", key = "#key")
    public String getFromDatabase(String key) {
        System.out.println("Retrieving from database");
        return "Fake";
    }
}
// 修改控制器代码
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;

@RestController
public class Controller {
    @Resource
    private DataService dataService;

    @PostMapping("/temp")
    public String retrieve() {
        return dataService.getFromDatabase("yes");
    }
}

方案2:自注入代理对象(应急可用,不推荐)

如果不想抽离类,可以在当前类中注入自身的代理对象,通过代理对象调用目标方法:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;

@RestController
public class Controller {
    // 注入自身的代理实例
    @Autowired
    private Controller selfProxy;

    @PostMapping("/temp")
    public String retrieve() {
        // 通过代理对象调用方法,触发缓存逻辑
        return selfProxy.getFromDatabase("yes");
    }

    @Cacheable(value = "databaseCache", key = "#key")
    public String getFromDatabase(String key) {
        System.out.println("Retrieving from database");
        return "Fake";
    }
}

注意:这种方式会破坏代码的分层结构,增加耦合度,仅适合临时应急使用。

方案3:使用AspectJ织入(复杂场景可选)

如果必须在类内部调用时触发AOP,可以用AspectJ代替Spring默认的动态代理,通过编译时或加载时织入实现方法拦截。但配置相对复杂,一般中小型项目不需要用到。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 16:30:36