我在 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
会使用缓存的结果。
什么可能导致此问题?
请勿将
@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
来说,接口方法更容易一些,而对于程序员来说,注释方法可能更容易。两者都可以。