Spring Cache @Cacheable-从同一个bean的另一个方法调用时不起作用


107

从同一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在第二次调用预期。但是,getEmployeeDataAService类中(getEmployeeEnrichedData)中调用该方法时,则未使用Cache。

这是Spring缓存的工作方式还是我缺少什么?


您对someDate参数使用相同的值吗?
Dewfy 2013年

@Dewfy是的,它是一样的
Bala

Answers:


158

我相信这是这样的。从我记得阅读的内容来看,生成了一个代理类,该代理类可以拦截所有请求并使用缓存的值进行响应,但是同一类内的“内部”调用将无法获取缓存的值。

来自https://code.google.com/p/ehcache-spring-annotations/wiki/UsingCacheable

仅拦截通过代理传入的外部方法调用。这意味着自调用实际上是目标对象中的一种方法,它调用目标对象的另一种方法,即使调用的方法标记有@Cacheable,也不会在运行时导致实际的缓存拦截。


1
好吧,如果您也使第二个调用可缓存,那么它将只有一个缓存未命中。也就是说,只有第一次调用getEmployeeEnrichedData会绕过缓存。对它的第二次调用将使用从第一次调用到getEmployeeEnrichedData的先前缓存的返回值。
肖恩·

1
@Bala我有同样的问题,我的解决方案是移动@Cacheable到DAO :(如果你有更好的解决方案,请让我知道,谢谢。
VAdaihiep

2
您还可以编写一个服务(例如CacheService),然后将所有要缓存的方法放入该服务中。在需要的地方自动连接服务并调用方法。对我的情况有所帮助。
DOUBL3P

从Spring 4.3开始,可以使用@Resource自动装配解决此问题,请参见示例stackoverflow.com/a/48867068/907576
radistao

1
同样,外部@Cacheable方法应该是public,它不适用于程序包专用方法。发现它很困难。
阿南德

36

从Spring 4.3开始,可以通过在标注上使用自动装配来解决此问题@Resource

@Component
@CacheConfig(cacheNames = "SphereClientFactoryCache")
public class CacheableSphereClientFactoryImpl implements SphereClientFactory {

    /**
     * 1. Self-autowired reference to proxified bean of this class.
     */
    @Resource
    private SphereClientFactory self;

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull TenantConfig tenantConfig) {
        // 2. call cached method using self-bean
        return self.createSphereClient(tenantConfig.getSphereClientConfig());
    }

    @Override
    @Cacheable(sync = true)
    public SphereClient createSphereClient(@Nonnull SphereClientConfig clientConfig) {
        return CtpClientConfigurationUtils.createSphereClient(clientConfig);
    }
}

2
在下面尝试了一下4.3.17,但没有用,self不通过代理进行的调用和(仍然)绕过了缓存。
Madbreaks

为我工作。缓存命中。到目前为止,我使用的是最新的spring依赖项。
Tomas Bisciak '19

我是唯一一个认为这会破坏模式,看起来像是单例混合等等的人吗?
2mia

我使用了spring boot starter版本-2.1.0.RELEASE,并且遇到了同样的问题。这种特殊的解决方案像一个魅力。
Deepan Prabhu Babu

18

下面的示例是我用来从同一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);
}
...

另请参见在Spring bean中启动新事务


1
例如applicationContext.getBean(SettingService.class);,访问应用程序上下文与依赖项注入相反。我建议避免这种风格。
SingleShot

2
是的,最好避免这种情况,但是我看不到针对此问题的更好解决方案。
molholm

10

这是我对仅少量使用同一类中的方法调用的小型项目所做的工作。强烈建议使用编码中的文档,因为对同事来说,它可能看起来很有趣。但是它易于测试,简单,快速实现,并为我省去了成熟的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);
        ...
    }
}

1
你能用AspectJ举例吗?
塞尔吉奥·比洛洛

这个答案是stackoverflow.com/a/34090850/1371329的副本。
jaco0646

3

在我的情况下,我添加变量:

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

}

在这种情况下,它将使用缓存。


2

使用静态编织在您的bean周围创建代理。在这种情况下,即使“内部”方法也可以正常工作


什么是“静态编织”?谷歌没有太大帮助。有什么指针可以理解这个概念吗?
巴拉

@Bala-例如,在我们的项目中,我们使用<iajc编译器(来自ant)来解析可缓存类的所有必要方面。
2013年

0

FactoryInternalCache为此,我使用带有内部缓存的内部内部bean():

@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="/programming/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="/programming/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);
    }
}
}

0

到目前为止,最简单的解决方案就是像这样引用:

AService.this.getEmployeeData(date);
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.