从同一个bean的另一个方法调用缓存方法时,Spring缓存无效。
这是一个以清晰的方式解释我的问题的例子。
配置:
<cache:annotation-driven cache-manager="myCacheManager" />
<bean id="myCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="myCache" />
</bean>
<!-- Ehcache library setup -->
<bean id="myCache"
class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:shared="true">
<property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>
<cache name="employeeData" maxElementsInMemory="100"/>
缓存服务:
@Named("aService")
public class AService {
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = getEmployeeData(date);
...
}
}
结果:
aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate);
output:
aService.getEmployeeEnrichedData(someDate);
output: Cache is not being used
getEmployeeData
方法调用在预期的第二次调用中使用缓存employeeData
。但是当在getEmployeeData
类(AService
)中调用getEmployeeEnrichedData
方法时,不会使用缓存。
这是Spring缓存的工作原理还是我错过了什么?
答案 0 :(得分:112)
我相信这是它的工作原理。根据我记得的内容,生成了一个代理类,它拦截所有请求并使用缓存值进行响应,但同一类中的“内部”调用不会获得缓存值。
来自https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable
只有通过代理进入的外部方法调用才有 截获。这意味着自我调用实际上是一种方法 在目标对象内调用目标对象的另一个方法, 即使在运行时也不会导致实际的缓存拦截 调用的方法用@Cacheable标记。
答案 1 :(得分:15)
以下示例是我用来从同一个bean中命中代理的,它类似于@ mario-eis'解决方案,但我发现它更具可读性(可能不是:-)。无论如何,我喜欢在服务级别保留@Cacheable注释:
@Service
@Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {
@Inject
private SettingRepository settingRepository;
@Inject
private ApplicationContext applicationContext;
@Override
@Cacheable("settingsCache")
public String findValue(String name) {
Setting setting = settingRepository.findOne(name);
if(setting == null){
return null;
}
return setting.getValue();
}
@Override
public Boolean findBoolean(String name) {
String value = getSpringProxy().findValue(name);
if (value == null) {
return null;
}
return Boolean.valueOf(value);
}
/**
* Use proxy to hit cache
*/
private SettingService getSpringProxy() {
return applicationContext.getBean(SettingService.class);
}
...
答案 2 :(得分:15)
从Spring 4.3开始,使用self-autowiring注释可以解决问题:
@Resource
答案 3 :(得分:8)
以下是我对同一类中只有少量方法调用的小项目所做的工作。强烈建议使用代码内文档,因为它可能会让同事感到惊讶。但它易于测试,简单,快速实现,并使我完全成熟的AspectJ仪器。但是,为了更加繁重的使用,我建议使用AspectJ解决方案。
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class AService {
private final AService _aService;
@Autowired
public AService(AService aService) {
_aService = aService;
}
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = _aService.getEmployeeData(date);
...
}
}
答案 4 :(得分:3)
是的,由于其他帖子中已经提到的原因,缓存不会发生。但是我会通过将该方法放到它自己的类(在这种情况下是服务)来解决问题。这样,您的代码将更易于维护/测试和理解。
@Service // or @Named("aService")
public class AService {
@Autowired //or how you inject your dependencies
private EmployeeService employeeService;
public List<EmployeeData> getEmployeeData(Date date){
employeeService.getEmployeeData(date);
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = getEmployeeData(date);
...
}
}
@Service // or @Named("employeeService")
public class EmployeeService {
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
println("This will be called only once for same date");
...
}
}
答案 5 :(得分:2)
使用静态编织在bean周围创建代理。在这种情况下,即使是“内部”方法也能正常工作
答案 6 :(得分:1)
在我的情况下,我添加了变量:
@Autowired
private AService aService;
因此,我使用getEmployeeData
来调用aService
方法
@Named("aService")
public class AService {
@Cacheable("employeeData")
public List<EmployeeData> getEmployeeData(Date date){
..println("Cache is not being used");
...
}
public List<EmployeeEnrichedData> getEmployeeEnrichedData(Date date){
List<EmployeeData> employeeData = aService.getEmployeeData(date);
...
}
}
在这种情况下,它将使用缓存。
答案 7 :(得分:1)
如果您从同一bean调用缓存的方法,它将被视为私有方法,并且注释将被忽略
答案 8 :(得分:0)
为此,我使用内部内部bean(FactoryInternalCache
)和真正的缓存:
@Component
public class CacheableClientFactoryImpl implements ClientFactory {
private final FactoryInternalCache factoryInternalCache;
@Autowired
public CacheableClientFactoryImpl(@Nonnull FactoryInternalCache factoryInternalCache) {
this.factoryInternalCache = factoryInternalCache;
}
/**
* Returns cached client instance from cache.
*/
@Override
public Client createClient(@Nonnull AggregatedConfig aggregateConfig) {
return factoryInternalCache.createClient(aggregateConfig.getClientConfig());
}
/**
* Returns cached client instance from cache.
*/
@Override
public Client createClient(@Nonnull ClientConfig clientConfig) {
return factoryInternalCache.createClient(clientConfig);
}
/**
* Spring caching feature works over AOP proxies, thus internal calls to cached methods don't work. That's why
* this internal bean is created: it "proxifies" overloaded {@code #createClient(...)} methods
* to real AOP proxified cacheable bean method {@link #createClient}.
*
* @see <a href="https://stackoverflow.com/questions/16899604/spring-cache-cacheable-not-working-while-calling-from-another-method-of-the-s">Spring Cache @Cacheable - not working while calling from another method of the same bean</a>
* @see <a href="https://stackoverflow.com/questions/12115996/spring-cache-cacheable-method-ignored-when-called-from-within-the-same-class">Spring cache @Cacheable method ignored when called from within the same class</a>
*/
@EnableCaching
@CacheConfig(cacheNames = "ClientFactoryCache")
static class FactoryInternalCache {
@Cacheable(sync = true)
public Client createClient(@Nonnull ClientConfig clientConfig) {
return ClientCreationUtils.createClient(clientConfig);
}
}
}
答案 9 :(得分:0)
到目前为止,最简单的解决方案是像这样引用:
AService.this.getEmployeeData(date);