@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
我有一个方法如下:
@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, theCachingConfigurer
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'sKeyGenerator
SPI. Normally,@EnableCaching
will configure Spring'sSimpleKeyGenerator
for this purpose, but when implementingCachingConfigurer
, a key generator must be provided explicitly. Returnnull
ornew SimpleKeyGenerator()
from this method if no customization is necessary.
CachingConfigurer
offers additional customization options: it is recommended to extend fromCachingConfigurerSupport
that provides a default implementation for all methods which can be useful if you do not need to customize everything. SeeCachingConfigurer
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