@Cacheable 不拦截方法,缓存永远为空

@Cacheable doesn't intercept the method, cache is always empty

我有一个方法如下:

@Cacheable(value = "SAMPLE")
public List<SomeObj> find() {
     // Method that initiates and returns the List<SomeObj> and takes around 2-3 seconds, does some logging too
}

并且我在我的配置之一中启用了缓存 类:

@EnableCaching
@Configuration
public SomeConf extends CachingConfigurerSupport {

    // Here I also initialize my classes with @Cacheable annotation

    @Bean
    @Override
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Collections.singletonList((new ConcurrentMapCache("SAMPLE"))));
        return cacheManager;
    }


    @Bean
    @Override
    public CacheResolver cacheResolver() {
        return new SimpleCacheResolver(cacheManager());
    }

    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return new SimpleKeyGenerator();
    }

}

我的 pom.xml 中有以下内容:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
    <version>1.5.14.RELEASE</version>
</dependency>

我声明 CacheManager 如下:

@Bean
public CacheManager cacheManager(){
    SimpleCacheManager cacheManager = new SimpleCacheManager();
    cacheManager.setCaches(Collections.singletonList((new ConcurrentMapCache("SAMPLE"))));
    return cacheManager;
}

当我将 @Autowired CacheManager 实例放入我的 @Service 之一时,我可以看到存在名称为 "SAMPLE" 的缓存,但其条目是总是空的。我一次又一次地调用方法find(),但它似乎没有填充缓存。

我试图将参数(比如 int a)放入 find() 方法并将其作为 key = "#a" 放入 @Cacheable,但没有任何改变。

当我尝试在隔离环境中重现该问题时,我发现它工作正常。但是当我添加依赖项(非开源公司库,其中也包含 EhCache 配置)时,它不起作用。我该如何调试它,我做错了什么?

更新:

我也尝试过在 @Cacheable 中使用 cacheManager = myCacheManager。运气不好。

更新二:

我正在使用 AspectJ 和 Spring AOP。我认为这可能与它有关。我试过 @EnableCaching(mode = AdviceMode.ASPECTJ)@EnableLoadTimeWeaving 但同样的事情。

更新3:

我终于能够重现这个问题,这里是:client-api-cache

基本上,当您 运行 应用程序和 telnet localhost 9000 在向其发送任何行后,它应该打印 NOT CACHED 一次,即使该方法在 [=33= 中被调用两次](第二个来自缓存)。但是它打印了两次。

确保您将缓存设置到缓存管理器中,如下所示。

@EnableCaching
@Configuration
public SomeConf {   

    @Bean
    public CacheManager cacheManager() {
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("find():List<SomeObj>")));
        return cacheManager;
    }


}

正如 Andrews S 指出的那样,这听起来确实像是在自动配置中碰撞缓存。

@EnableCaching's javadoc 有一些关于 cacheManager 是如何 selected 的有用注释,特别是关于如何进行的想法。在这种情况下你要做的是建立一个 CachingConfigurer 到 select 你的缓存 - 也许你可以扩展 CachingConfigurerSupport (如下例所示)并完成。

A bean of type CacheManager must be registered, as there is no reasonable default that the framework can use as a convention. And whereas the <cache:annotation-driven> element assumes a bean named "cacheManager", @EnableCaching searches for a cache manager bean by type. Therefore, naming of the cache manager bean method is not significant.

For those that wish to establish a more direct relationship between @EnableCaching and the exact cache manager bean to be used, the CachingConfigurer callback interface may be implemented. Notice the @Override-annotated methods below:

@Configuration
@EnableCaching
public class AppConfig extends CachingConfigurerSupport {

     @Bean
     public MyService myService() {
         // configure and return a class having @Cacheable methods
         return new MyService();
     }

     @Bean
     @Override
     public CacheManager cacheManager() {
         // configure and return an implementation of Spring's CacheManager SPI
         SimpleCacheManager cacheManager = new SimpleCacheManager();
         cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
         return cacheManager;
     }

     @Bean
     @Override
     public KeyGenerator keyGenerator() {
         // configure and return an implementation of Spring's KeyGenerator SPI
         return new MyKeyGenerator();
     }
}

This approach may be desirable simply because it is more explicit, or it may be necessary in order to distinguish between two CacheManager beans present in the same container.

Notice also the keyGenerator method in the example above. This allows for customizing the strategy for cache key generation, per Spring's KeyGenerator SPI. Normally, @EnableCaching will configure Spring's SimpleKeyGenerator for this purpose, but when implementing CachingConfigurer, a key generator must be provided explicitly. Return null or new SimpleKeyGenerator() from this method if no customization is necessary.

CachingConfigurer offers additional customization options: it is recommended to extend from CachingConfigurerSupport that provides a default implementation for all methods which can be useful if you do not need to customize everything. See CachingConfigurer Javadoc for further details.

您可能还会发现此线程有用:How to have multiple cache manager configuration in spring cache java

编辑:

谢天谢地,我有一个使用缓存的项目,并写下了这一点:

@Bean
@Override
public CacheResolver cacheResolver() {
    return new SimpleCacheResolver(cacheManager()) { 

        @Override
        protected Collection<String> getCacheNames(CacheOperationInvocationContext<?> context) {
            Collection<String> toReturn = super.getCacheNames(context);
            toReturn.forEach(System.out::println);
            return toReturn;
        }

        @Override
        public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> context) {
            System.out.println(Arrays.toString(context.getArgs()));
            System.out.println(context.getClass());
            System.out.println(context.getMethod());
            System.out.println(context.getOperation());
            return super.resolveCaches(context);
        }
    };
}

除了看到我建立的缓存名称弹出,我确实注意到上下文正在输出:

[]

class org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext public abstract ...transferobjects.CacheContainer ...service.LookupService.getCacheSelections()

Builder[public ...transferobjects.CacheContainer ...dao.LookupFacade.getCacheSelections()] caches=[cacheselections] | key='' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='true'

考虑到您的方面问题,context.getClass() 输出很有意义。也就是说,我自己的代码中有一个非常相似的 logging/timing 方面,不会对其余缓存造成任何混淆。试用我的解析器,看看输出是否对针对缓存代码进行的调用具有指导意义。

编辑#2:

TLDR 问题似乎是我们期望 @Cacheable 在它不能工作的地方工作 - 主要是因为该框架尚未完全启动。您正在使用 InitializingBean,我尝试用 @PostConstruct 替换该功能,但都失败了。

Spring cache using @Cacheable during @PostConstruct does not work

我收到了你的 github 密码。我最初 运行 进入了您在另一个线程中报告的阻塞问题,但是为了一次只做一件事,我只是注释掉了阻塞代码并直接调用了 service.cachedMethod()

我 运行 你的 jar 文件 --trace 并注意到 cacheManager 已被扫描和创建,但它在被调用时最初对我来说并不明显。所以,没什么大不了的,我只是让 bean 方法抛出一个 RuntimeException。这样做后,我注意到您的 NOT CACHED 行都在该调用之前打印 - 另一个提示表明事情没有按预期设置。

最后,我在您的控制器的 afterPropertiesSet() 方法中添加了 System.out.println(service.toString()); 并看到 com.company.client.api.domain.CachedServiceImpl@2bbfb8b - 您的对象,而不是代理对象。因此没有缓存。

因此,一般来说,您需要重新设计您尝试启用此功能的方式。

根本原因是您误用了"afterPropertiesSet"。所以,你正在做的是无限循环,永远不会将控制权交还给 Spring 管道,因此 Spring 无法正确初始化缓存设施。

查看解决问题的代码:https://dumpz.org/cbx8h28KeAss