Spring Cache @Cacheable – ne fonctionne pas en appelant d’une autre méthode du même bean

Le cache Spring ne fonctionne pas lors de l’appel de la 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:

         

Service mis en cache:

 @Named("aService") public class AService { @Cacheable("employeeData") public List getEmployeeData(Date date){ ..println("Cache is not being used"); ... } public List getEmployeeEnrichedData(Date date){ List 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’ getEmployeeData 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 fonctionne la cache de spring ou est-ce que je manque quelque chose?

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

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

Seuls les appels de méthodes externes provenant du proxy sont interceptés. Cela signifie que l’auto-invocation, en fait, une méthode de l’object cible appelant une autre méthode de l’object cible n’entraînera pas une véritable interception du cache à l’exécution, même si la méthode invoquée est marquée avec @Cacheable.

L’exemple ci-dessous est ce que j’utilise pour bash le proxy depuis le même bean, il est similaire à la solution @ mario-eis, mais je le trouve un peu plus lisible (peut-être pas :-)). En tout cas, j’aime 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 Ssortingng findValue(Ssortingng name) { Setting setting = settingRepository.findOne(name); if(setting == null){ return null; } return setting.getValue(); } @Override public Boolean findBoolean(Ssortingng name) { Ssortingng 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 Commencer une nouvelle transaction dans Spring bean

Depuis le spring 4.3, le problème a pu être résolu en utilisant l’ auto-activation 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); } } 

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 dans le code est fortement recommandée, car elle peut sembler difficile pour les collègues. Mais il est facile à tester, simple, rapide à réaliser et m’épargne la pleine instrumentation AspectJ. Cependant, pour un usage plus intensif, 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 getEmployeeData(Date date){ ..println("Cache is not being used"); ... } public List getEmployeeEnrichedData(Date date){ List employeeData = _aService.getEmployeeData(date); ... } } 

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

Une autre solution simple et efficace: déplacez l’annotation @Cacheable vers le niveau inférieur (DAO). Le problème rest à l’intérieur de la classe DAO, mais résolu pour le service.

J’utilise le bean interne interne ( FactoryInternalCache ) avec un cache réel à cet effet:

 @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 Spring Cache @Cacheable - not working while calling from another method of the same bean * @see Spring cache @Cacheable method ignored when called from within the same class */ @EnableCaching @CacheConfig(cacheNames = "ClientFactoryCache") static class FactoryInternalCache { @Cacheable(sync = true) public Client createClient(@Nonnull ClientConfig clientConfig) { return ClientCreationUtils.createClient(clientConfig); } } }