@Cacheable不会拦截该方法,缓存始终为空

时间:2018-09-26 12:57:53

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

我有以下方法:

@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配置),它不起作用。我该如何调试,我在做什么错了?

更新

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

更新2:

我正在使用AspectJ和Spring AOP。我认为这可能与它有关。我已经尝试将@EnableCaching(mode = AdviceMode.ASPECTJ)@EnableLoadTimeWeaving一起使用,但是还是一样。

更新3:

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

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

3 个答案:

答案 0 :(得分:3)

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

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

答案 1 :(得分:1)

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

@EnableCaching's javadoc有一些有关如何选择cacheManager的有用注释,尤其是关于如何进行操作的想法。在这种情况下,您要做的是建立一个CachingConfigurer以选择您的缓存-也许您只需扩展CachingConfigurerSupport(如下例所示)即可。

  

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

     

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

@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时,必须显式提供密钥生成器。如果不需要自定义,则从此方法返回nullnew 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] |键=''| keyGenerator =''| | cacheManager =''| | cacheResolver =''|条件=''|除非=''| sync ='true'

考虑到您的纵横比问题,context.getClass()的输出很有趣。就是说,我在自己的代码中有一个非常类似的日志记录/计时方面,不会对其余的缓存造成任何混乱。试用我的解析器,看看输出是否有启发性,说明对缓存代码进行的调用。

编辑#2:

TLDR问题似乎是我们期望@Cacheable在无法运行的地方工作-主要是由于该框架尚未完全完善。您正在使用InitializingBean,但我尝试用@PostConstruct替换该功能,但都失败了。

Spring cache using @Cacheable during @PostConstruct does not work

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

我使用--trace运行您的jar文件,并注意到已扫描并创建了cacheManager,但最初对它的调用对我来说并不明显。因此,没什么大不了的,我只是让bean方法抛出了RuntimeException。完成此操作后,我注意到您的NOT CACHED行都在该调用之前打印-另一个提示是未按预期进行设置。

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

因此,通常,您需要重新尝试尝试启用此功能的方式。

答案 2 :(得分:-1)

确保按以下方式设置缓存到cachemanager中。

@EnableCaching
@Configuration
public SomeConf {   

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


}