@Cacheable 和启动时的初始化

@Cacheable and initialization during startup

我想在 spring 引导应用程序启动期间初始化缓存中的所有条目(从数据库加载内容)。理想情况下,这是在应用程序准备就绪之前完成的。所以我在 @PostConstruct 中实现了所有加载。我注意到,缓存尚未在 @PostContruct 中设置,我按照一些提示在 ApplicationReadyEvent 中进行了此类初始化。但是,这仍然无法按预期工作:

即使我已经在 ApplicationReadyEvent 中调用了一个 @Cacheable 方法,第二次调用重新进入该方法而不是使用缓存。

我的服务:

@Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {

  @Cacheable("entry")
  public List<String> getEntry() {
    System.out.println("getEntry called!");
    return Arrays.asList("aaa", "bbb");
  }

  @Override
  public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
    System.out.println("*** onApplicationEvent");
    getEntry();
  }
}

我的 Caffeine CacheManager 配置:

@Configuration
@EnableCaching
public class CachingConfig {

  @Bean
  public CacheManager cacheManager() {
    List<CaffeineCache> caffeineCaches = chacheList(Arrays.asList(
        "entry"
    ));

    SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
    simpleCacheManager.setCaches(caffeineCaches);
    System.out.println("*** @Bean CacheManager");
    return simpleCacheManager;
  }

  private List<CaffeineCache> chacheList(List<String> cacheNames) {
    return cacheNames.stream().map(s -> new CaffeineCache(s, Caffeine.newBuilder().build()))
        .collect(Collectors.toList());
  }

}

使用服务的简单 REST 端点:

@RestController
public class MyController {

  @Autowired
  MyService myService;

  @GetMapping("/test")
  public void test()
  {
    System.out.println("*** GET /test");
    myService.getEntry();
  }
}

如果我启动应用程序并执行两个 GET /test,我会得到以下输出:

INFO 20120  --- [           main] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 907 ms
*** @Bean CacheManager
INFO 20120  --- [           main] o.s.s.concurrent.ThreadPoolTaskExecutor  : Initializing ExecutorService 'applicationTaskExecutor'
INFO 20120  --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
INFO 20120  --- [           main] com.example.demo.DemoApplication         : Started DemoApplication in 1.639 seconds (JVM running for 2.473)
*** onApplicationEvent
*** getEntry called!
INFO 20120  --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
INFO 20120  --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
INFO 20120  --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 4 ms
*** GET /test
*** getEntry called!
*** GET /test

那么为什么第二次调用MyService.getEntry(即"Startup"之后的第一次调用)又进入了代码呢?

最后,我需要一个解决方案,它在应用程序完成启动之前执行第一次加载 - 即我将尝试 ContextRefreshedEvent 或再次 @PostConstruct(和 @Autowire CacheManager在执行 @PostConstruct 之前配置它)。但第一步是让此示例在此处按预期运行。

好吧,愚蠢的错误:在我的服务中,对 getEntry() 的调用必须通过代理对象而不是直接完成:

@Service
public class MyService implements ApplicationListener<ApplicationReadyEvent {

  @Autowired
  MyService self;

  @Cacheable("entry")
  public List<String> getEntry() {
    System.out.println("getEntry called!");
    return Arrays.asList("aaa", "bbb");
  }

  @Override
  public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) {
    System.out.println("*** onApplicationEvent");
    self.getEntry();
  }
}