Spring Boot @Cacheable注解在@PostConstruct及Groovy脚本中失效问题
核心原因
Spring的@Cacheable注解依赖动态代理实现缓存逻辑,而代理对象的创建时机在Bean生命周期的初始化阶段之后,这直接导致了@PostConstruct阶段调用方法时缓存失效,同时也是Groovy脚本中缓存不生效的关键原因。
具体场景分析
- @PostConstruct调用失效的原因
Spring Bean的完整生命周期流程为:
- 实例化Bean → 依赖注入 → 执行
@PostConstruct方法 → 生成AOP代理并替换容器中的原始Bean实例
当你在SpringTestApplication的@PostConstruct方法中调用javaStarter.invokeTestMethod()时,JavaService的代理对象尚未生成,调用的是JavaService的原始对象方法,@Cacheable的拦截逻辑根本不会被触发,自然无法生效。
REST控制器调用正常的原因
REST控制器的方法是在Spring容器完全初始化完成后,由外部HTTP请求触发调用的。此时容器中的JavaService已经是代理对象,调用标注@Cacheable的方法时,代理会先执行缓存校验逻辑,命中缓存则直接返回结果,未命中才会调用原始方法并写入缓存。Groovy脚本中的问题
Groovy脚本中缓存未生效,本质和@PostConstruct场景一致:要么脚本在容器未完全初始化时就获取了JavaService实例(此时还是原始对象),要么脚本通过非Spring容器的方式直接实例化了JavaService,导致代理逻辑无法介入。
解决方案
1. 容器初始化完成后再调用(解决@PostConstruct场景)
实现ApplicationListener<ContextRefreshedEvent>接口,监听容器初始化完成的事件,在回调中调用目标方法:
package test.spring.springtest; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.stereotype.Component; @Component public class CacheableStartupListener implements ApplicationListener<ContextRefreshedEvent> { private final JavaStarter javaStarter; public CacheableStartupListener(JavaStarter javaStarter) { this.javaStarter = javaStarter; } @Override public void onApplicationEvent(ContextRefreshedEvent event) { // 仅在根容器初始化完成后执行,避免父子容器重复触发 if (event.getApplicationContext().getParent() == null) { javaStarter.invokeTestMethod("test"); } } }
替换掉原SpringTestApplication中的@PostConstruct方法,运行后会看到仅第一次输出Cache is not being used,后续两次直接使用缓存结果,符合预期。
2. 确保Groovy脚本正确获取代理对象
在Groovy脚本中,必须通过Spring容器的getBean()方法获取JavaService实例,且确保脚本在容器完全初始化后执行:
// 示例Groovy脚本代码 def context = // 已初始化完成的Spring ApplicationContext实例 def javaService = context.getBean(JavaService.class) // 此时调用的是代理对象的方法,缓存逻辑会生效 javaService.testMethod("test")
禁止直接实例化JavaService对象,也不要在容器启动过程中过早获取Bean实例。
内容的提问来源于stack exchange,提问作者Denis Konev

