启动时无法使用@CachePut加载数据

问题描述 投票:0回答:1

我在 Tomcat 上有一个 Spring (5.3.35) 程序,它会自动更新缓存的结果(无参数):

@Component
class ClientImpl
    implements Client, ClientCacheManager {

    @Override
    @Cacheable(Configurator.CACHE_NAME)
    public String queryWithCache() {
        return this.query();
    }

    @Override
    @CachePut(Configurator.CACHE_NAME)
    @Scheduled(cron = "#{configuration.cacheUpdateCron}")
    public String updateCache() {
        log.debug("Updating query cache");
        return this.query();
    }

    @Override
    public String query() {
        [...] // do stuff, return String
    }
}

有两个接口:

  • Client
    ,定义
    queryWithCache
    queryWithCache
    当客户登录时调用。
  • ClientCacheManager
    ,定义
    updateCache
    updateCache
    不是由用户操作调用,而是由
    @PostConstruct
    方法和调度程序调用。

因为我希望在启动时,在任何客户登录之前,缓存已被填充,所以我确实定义了一个

CacheInitializer
组件:

@Component
public class CacheInitializer{

    private static final Logger log = LoggerFactory.getLogger(InicializadorCacheJson.class);

    @Autowired
    private ClientCacheManager clientCacheManager;

    @PostConstruct
    void onStartup() {
        log.debug("Startup data loading");
        this.clientCacheManager.updateCache();
        log.debug("Startup data loaded");
    }
}

服务器启动时,显示“启动数据加载/已加载”消息,并且没有任何错误指示。但是,用户第一次登录时,会调用

queryWithCache
,并且不会返回缓存的数据,而是调用
query
方法。此后,后续调用
queryWithCache
会使用缓存的结果。

什么可能导致此问题?

java spring caching
1个回答
0
投票

请勿将

@PostConstruct
用于此类任务。这些
@PostConstruct
方法在对象构造和依赖注入之后调用。不保证此时已经创建了应用 AOP 的代理。引导您在早期实例上调用该方法,而无需缓存行为。

您应该做的是在确定应用程序上下文已完全加载后推迟此调用。现在,在 Spring Boot 应用程序中,通过提供将在上下文初始化后调用的

ApplicationRunner
,这将相当容易。当您在普通的 Spring 应用程序中时,您可以使用
ApplicationListener
并对
ContextRefreshedEvent
做出反应,当
ApplicationContext
准备使用时,它将被触发。

@Component
public class InitializationListener implements ApplicationListener<ContextRefreshedEvent> {

  private final Logger log = LoggerFactory.getLogger(getClass());

  public void onApplicationEvent(ContextRefreshedEvent evt) {
    var ccm = evt.getApplicationContext().getBean(ClientCacheManager.class);
    log.debug("Startup data loading");
    ccm.updateCache();
    log.debug("Startup data loaded");
  }
}

ContextRefreshedEvent
包含指向
ApplicationContext
的链接。您可以使用这个
ApplicationContext
来获取调用其方法所需的bean。

如果您更喜欢使用注入,请务必用

@Lazy
进行标记,以确保不会触发 beans 的早期初始化。

@Component
public class InitializationListener implements ApplicationListener<ContextRefreshedEvent> {

  private final Logger log = LoggerFactory.getLogger(getClass());
  private final ClientCacheManager ccm;

  public InitializationListener(@Lazy ClientCacheManager ccm) {
    this.ccm=ccm;
  }
  public void onApplicationEvent(ContextRefreshedEvent evt) {
    log.debug("Startup data loading");
    this.ccm.updateCache();
    log.debug("Startup data loaded");
  }
}

最后,如果您不想扩展

ApplicationListener
,您还可以在方法上使用
@EventListener
注释。

@EventListener(ContextRefreshedEvent.class)
public void onContextRefreshed() {
  // your logic here.
}

两者都可以,对于

ApplicationContext
来说,接口方法更容易一些,而对于程序员来说,注释方法可能更容易。两者都可以。

© www.soinside.com 2019 - 2024. All rights reserved.