【问题标题】:@Cacheable doesn't intercept the method, cache is always empty@Cacheable 不拦截方法,缓存始终为空
【发布时间】:2018-09-26 12:57:52
【问题描述】:

我有一个方法如下:

@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;
}

当我将@AutowiredCacheManager 实例放入我的@Services 之一时,我可以看到存在一个名为"SAMPLE" 的缓存,但其条目始终为空。我一遍又一遍地调用方法find(),但它似乎没有填充缓存。

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

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

更新:

我也尝试在@Cacheable 中使用cacheManager = myCacheManager。没有运气。

更新 2:

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

更新 3:

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

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

【问题讨论】:

  • 类路径中是否有定义名为“find():List”的缓存的 ehcache.xml 文件?
  • 感谢您的评论。这里有两种情况:1)我没有显式配置一个CacheManager,而Spring配置了一个GuavaCacheManager,所以ehcache.xml是无关紧要的。 2)当我用EhCacheManager明确定义CacheManager@Bean并放置一个定义名为“find():List”的缓存的ehcache.xml(实际上它不接受&lt;字符所以我不得不替换缓存名称但仍然)。在这两种情况下,都无法正常工作。
  • @AndrewS 对于更新后的问题,您还能想到什么吗?谢谢。
  • 你能确认SomeConf被扫描了吗? find() 没有参数,所以不确定它会使用什么作为键 - 也许获取 Spring 源代码以查看缓存代理类是否实际被调用。
  • @AndrewS 我可以确认@SomeConf 已被扫描。我还尝试将参数(int a)放入find() 方法并将其作为key = "#a" 放入@Cacheable,但没有任何改变。

标签: java spring spring-boot caching spring-cache


【解决方案1】:

根本原因是您误用了“afterPropertiesSet”。因此,您正在做的是无限循环并且永远不会将控制权传递回 Spring 管道,因此 Spring 无法正确初始化缓存设施。

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

【讨论】:

  • 是的,我知道了!
【解决方案2】:

正如 Andrews S 所说,这听起来确实像自动配置中的缓存冲突。

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

CacheManager 类型的 bean 必须注册,因为没有合理的默认值可供框架用作约定。而&lt;cache:annotation-driven&gt; 元素假定一个名为“cacheManager”的bean,@EnableCaching 按类型搜索缓存管理器bean。因此,缓存管理器 bean 方法的命名并不重要。

对于那些希望在@EnableCaching 和要使用的确切缓存管理器bean 之间建立更直接关系的用户,可以实现CachingConfigurer 回调接口。注意下面的@Override-annotated 方法:

@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();
     }
}

这种方法可能是可取的,因为它更明确,或者可能有必要区分同一容器中存在的两个CacheManager bean。

还要注意上面示例中的keyGenerator 方法。这允许根据 Spring 的KeyGenerator SPI 自定义缓存键生成策略。通常,@EnableCaching 会为此配置 Spring 的 SimpleKeyGenerator,但在实现 CachingConfigurer 时,必须显式提供密钥生成器。如果不需要自定义,则从此方法返回 null 或 new SimpleKeyGenerator()。

CachingConfigurer 提供额外的自定义选项:建议从 CachingConfigurerSupport 扩展,它为所有方法提供默认实现,如果您不需要自定义所有方法,这可能很有用。详情请见CachingConfigurer Javadoc。

您可能还会发现此线程很有用: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);
        }
    };
}

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

[]

类 org.springframework.cache.interceptor.CacheAspectSupport$CacheOperationContext 公共抽象 ...transferobjects.CacheContainer ...service.LookupService.getCacheSelections()

Builder[public ...transferobjects.CacheContainer ...dao.LookupFacade.getCacheSelections()] caches=[cacheselections] |键='' |密钥生成器='' |缓存管理器='' |缓存解析器='' |条件='' |除非='' |同步='真'

鉴于您的 Aspect 问题,context.getClass() 输出很有趣。也就是说,我自己的代码中有一个非常相似的日志记录/计时方面,不会对其余的缓存造成任何混淆。试试我的解析器,看看输出是否能说明正在对缓存代码进行哪些调用。

编辑#2:

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

Spring cache using @Cacheable during @PostConstruct does not work

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

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

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

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

【讨论】:

  • 感谢您的回答。这正是我所做的,没有任何区别。但我不能让它工作。
  • @HasanCanSaral 所以你已经修改了你的本地@Configuration SomeConf 以扩展CachingConfigurerSupport?您可以使用当前快照更新您的帖子,但我想您也可以尝试覆盖 cacheResolver() 以尝试调试 CacheOperationInvocationContext 内容。
  • 我已经更新了这个问题。第 5 天,各种尝试,但仍然无法正常工作......
  • 我已经复制了这个问题并把它放到了 GitHub 上。请找到它here。
  • 我已将afterPropertiesSet 中的阻塞代码放入一个新线程,让框架完全启动。它现在正在工作。谢谢。
【解决方案3】:

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

@EnableCaching
@Configuration
public SomeConf {   

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


}

【讨论】:

  • 感谢您的回答,但这无济于事。 Spring 实际上自动配置了一个CacheManager,但即使我按照您的说明手动配置了一个,它也不会被填充。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-05-07
  • 2014-01-17
  • 2018-02-07
  • 2018-03-05
  • 1970-01-01
  • 2020-12-29
  • 1970-01-01
相关资源
最近更新 更多