web-dev-qa-db-fra.com

Spring Cache @Cacheable - ne fonctionne pas lors de l'appel d'une autre méthode du même bean

Le cache Spring ne fonctionne pas lors de l'appel d'une méthode en cache à partir d'une autre méthode du même bean.

Voici un exemple pour expliquer mon problème de manière claire.

Configuration:

<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"/>  

Service en cache:

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

}

Résultat :

aService.getEmployeeData(someDate);
output: Cache is not being used
aService.getEmployeeData(someDate); 
output: 
aService.getEmployeeEnrichedData(someDate); 
output: Cache is not being used

L'appel à la méthode getEmployeeData utilise le cache employeeData dans le deuxième appel comme prévu. Mais lorsque la méthode getEmployeeData est appelée dans la classe AService (dans getEmployeeEnrichedData), le cache n'est pas utilisé.

Est-ce ainsi que la cache de printemps fonctionne ou est-ce que je manque quelque chose?  

53
Bala

Je crois que c'est comme ça que ça marche. D'après ce que je me souviens d'avoir lu, il existe une classe proxy générée qui intercepte toutes les demandes et répond avec la valeur en cache, mais les appels «internes» au sein de la même classe n'obtiendront pas la valeur en cache.

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

Seuls les appels de méthode externes entrant par le biais du proxy sont intercepté. Cela signifie que l’invocation automatique est en fait une méthode dans l'objet cible appelant une autre méthode de l'objet cible, n'entraînera pas d'interception de cache réelle au moment de l'exécution, même si le fichier La méthode invoquée est marquée avec @Cacheable.

97
Shawn D.

L'exemple ci-dessous est ce que j'utilise pour frapper le proxy depuis le même bean. C'est similaire à la solution de @ mario-eis, mais je la trouve un peu plus lisible (peut-être que ce n'est pas :-). Quoi qu'il en soit, j'aime bien garder les annotations @Cacheable au niveau du service:

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

Voir aussi Lancement d'une nouvelle transaction dans Spring bean

15
molholm

Depuis Spring 4.3, le problème pouvait être résolu en utilisant auto-auto-câblage sur l'annotation @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);
    }
}
12
radistao

Voici ce que je fais pour les petits projets avec une utilisation marginale des appels de méthode dans la même classe. La documentation en code est fortement recommandée, car elle peut paraître compliquée à vos collègues. Mais c’est facile à tester, simple, rapide à réaliser et me permet d’épargner toute l’instrumentation AspectJ. Cependant, pour une utilisation plus intensive, je conseillerais la solution 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);
        ...
    }
}
8
Mario Eis

Utilisez le tissage statique pour créer un proxy autour de votre haricot. Dans ce cas, même les méthodes 'internes' fonctionneraient correctement

2
Dewfy

Dans mon cas, j'ajoute une variable:

@Autowired
private AService  aService;

J'appelle donc la méthode getEmployeeData à l'aide de 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);
    ...
}

}

Il utilisera le cache dans ce cas.

1
Ibtissam Ibtissama

J'utilise un haricot interne (FactoryInternalCache) avec un cache réel à cette fin:

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