2013-06-03 65 views
38

當從同一個bean的另一個方法調用緩存方法時,Spring緩存不工作。Spring Cache @Cacheable - 從同一個bean的另一個方法調用時不工作

下面是一個以清晰的方式解釋我的問題的例子。

配置:

<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)內被調用時,緩存沒有被使用。

這是彈簧緩存如何工作,或者我錯過了什麼?

+0

「someDate」參數是否使用相同的值? – Dewfy

+0

@Dewfy是的,它是一樣的 – Bala

回答

66

我相信這是它的工作原理。從我記得讀到的,有一個生成的代理類攔截所有請求並用緩存的值進行響應,但同一類內的「內部」調用不會獲取緩存的值。

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

只有外部方法調用通過代理進來的是 截獲。這意味着調用目標對象的另一個方法的目標對象 中的方法 實際上不會導致實際的緩存在運行時被攔截,即使調用的方法標記爲@Cacheable。

+0

是否有任何解決方法來利用緩存? – Bala

+0

好吧,如果你再次調用Cacheable,它只會有一個緩存未命中。也就是說,只有第一次調用getEmployeeEnrichedData纔會繞過緩存。第二次調用它將使用從第一次調用getEmployeeEnrichedData以前緩存的返回。 –

+0

@Bala我有同樣的問題,我的解決方案是移動'@ Cacheable'到DAO :(如果你有更好的解決方案,請讓我知道,謝謝。 – VAdaihiep

2

使用靜態編織在您的bean周圍創建代理。在這種情況下,即使是「內部」方法也可以正常工作

+0

什麼是「靜態編織」?谷歌沒有多大幫助。任何理解這些概念的指針? – Bala

+0

@Bala - 就我們的項目而言,我們使用' Dewfy

4

下面是我爲小型項目所做的工作,在同一個類中只有邊際使用方法調用。代碼文檔強烈建議,因爲它可能看起來對同事很有幫助。但它易於測試,操作簡單,快速實現,併爲我提供了完整的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的例子嗎? –

0

另一種簡單的和很好的解決方案:移動@Cacheable批註下(DAO)的水平。問題仍然存在於DAO類中,但解決了Service。

9

下面的例子是我用來從同一個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); 
} 
... 

參見Starting new transaction in Spring bean

+0

訪問應用程序的上下文,例如'applicationContext.getBean(SettingService.class);',是o依賴注入的重要性。我建議避免這種風格。 – SingleShot

+0

是的,它會更好地避免它,但我沒有看到這個問題的更好的解決方案。 – molholm

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); 
    } 
} 
} 
1

自Spring 4.3以來,問題可以通過使用self-autowiring而不是@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); 
    } 
} 
相關問題