Site icon Blog Zenika

Spring Cache

Introduction

La mise en cache a toujours été un besoin important pour à la fois améliorer les performances d’une application et alléger sa charge de travail. De plus, son utilité est particulièrement évidente aujourd’hui avec les applications Web qui peuvent être amenées à gérer des milliers de visiteurs concurrents.D’un point de vue architectural, la gestion du cache est orthogonale à la logique métier de l’application et pour cette raison, elle devrait avoir un impact minimal sur le développement de l’application elle-même.

Depuis la version 3.1, Spring fournit une api pour la gestion de cache, semblable à la gestion déclarative des transactions. L’abstraction de la mise en cache permet une utilisation cohérente des différentes solutions de mise en cache avec un impact minimal sur le code.
Le cache Spring est appliqué à des méthodes Java. Au premier appel d’une méthode avec une combinaison de paramètres, Spring stocke sa valeur de retour dans le cache. Ainsi, l’appel suivant se verra directement servir la valeur venant du cache sans avoir besoin d’appeler le traitement derrière qui peut être couteux. Le tout est appliqué de façon transparente sans impacter l’appelant.
Dans cet article nous allons voir deux implémentations différentes du stockage de cache avec Spring.

Utilisation

La mise en cache d’une méthode avec Spring est une opération simple et transparente, on doit annoter notre méthode via l’annotation @Cacheable.

@Cacheable(value= "dataCache")
public Reponse getDatas(Long param1, String param2){ }

dataCache est le nom du gestionnaire de cache associé.
Au premier appel a la méthode, Spring cache la reponse, identifiée par une clé unique calculer sur la base de hashcode des paramètres d’appels < param1, param2>, et pour le énième appel avec les mêmes paramètres, Spring retourne la réponse déjà cacher sans avoir ré-exécuter la méthode.
Aussi, il est possible d’associé plus qu’un seul cache à notre méthode

@Cacheable({"dataCache",”default”})
public Reponse getDatas(Long param1, String param2){   }

Dans ce cas, chacun des caches sera vérifié avant d’exécuter la méthode, si au moins un cache est trouvé, alors la valeur associée sera retourné.

Génération des clés de cache

L’algorithme de base d’une gestion de cache est relativement trivial. Le cache est une zone mémoire dans laquelle on stocke des objets dont chacun est identifié par une clé unique calculée, on utilise en général les Maps pour stocker un cache. L’algorithme de récupération d’un objet est donc le suivant :

on le renvoie

on recalcule l’objet réel
on met l’objet dans le cache associé à sa clé
on renvoie l’objet
De même Spring utilise un KeyGenerator basé sur le hachage simple, qui calcule la clé sur la base des tables de hachage des objets passés en paramètre de la méthode.

Cache personnalisé

La clé de cache (key)

Il est tout à fait probable que les méthodes cibles ont différentes signatures qui ne peuvent pas être simplement mappées, cela tend à devenir évident lorsque la méthode cible a plusieurs arguments dont seulement certains sont adaptés pour la mise en cache (alors que le reste n’est utilisé que par la logique de la méthode)

@Cacheable(value= "dataCache")
public Reponse getDatas(Long param1, String param2, boolean param3){   }

Pour de tels cas, l’annotation @ Cacheable permet au développeur de spécifier la manière dont la clé de cache est générée. Le développeur peut utiliser SpEL pour choisir les arguments d’intérêt (ou leurs propriétés imbriquées).

@Cacheable(value= "dataCache", key="#param2")
public Reponse getDatas(Long param1, String param2, boolean param3){     }

Dans ce cas, la clé de cache sera calculée seulement avec le deuxième paramètre <param2>.
Spring permet aussi de spécifier des propriétés imbriquées :

@Cacheable(value="dataCache", key=#param2.name")
 public Reponse getDatas(Long param1, Data param2, boolean param3){}

Dans ce cas, la clé de cache sera calculée avec l’attribut name du paramètre <param2>
Les exemples ci-dessus montrent comment il est simple de sélectionner certain arguments ou une de ses propriétés.

Condition de la mise en cache

Parfois, une méthode pourrait ne pas être appropriée pour mettre en cache tout le temps mais sous certaines conditions. Les annotations de cache supportent cette fonctionnalité. Le paramètre condition prend une expression SpEL qui est évaluée à true ou false. Donc si la condition est vraie, la méthode sera mise en cache.

@Cacheable(value= "dataCache", key="#param2", condition="#param2.length<64")
public Reponse getDatas(Long param1, String param2, boolean param3){   }

Dans ce cas, la méthode est mise en cache si et seulement si la taille du deuxième paramètre est inferieure a 64.

L’annotation @CacheEvict

Le cache Spring permet non seulement la population d’un magasin de cache mais aussi son expulsion. Ce processus est utile pour supprimer les données obsolètes ou inutilisées de la mémoire cache. Opposée à @ Cacheable, l’annotation @ CacheEvict délimite des méthodes qui effectuent l’expulsion de cache, ce sont des méthodes qui agissent comme des déclencheurs de suppression des données à partir du cache. @ CacheEvict nécessite un (ou plusieurs) caches qui sont touchés par l’action.

@CacheEvict(value= "dataCache")
public void reloadData(){   }

Cette option est très pratique lorsqu’une région de cache entier doit être vidée. Il est important de noter que les méthodes void peuvent être utilisées avec @ CacheEvict, ces méthodes agissent comme des déclencheurs de suppression de cache, les valeurs de retour sont ignorées (comme elles n’interagissent pas avec le cache) .ce n’est pas le cas avec @ Cacheable qui ajoute / mettre à jour les données dans le cache et nécessite donc un retour.

L’annotation @CachePut

Forcer la mise à jour d’une entrée de cache. Par opposition à l’annotation @Cacheable, cette annotation provoque toujours l’exécution de la méthode et le stockage de son résultat dans la mémoire cache.

@CachePut(value= "dataCache", key="#param2.name")
public Reponse getDatas(Long param1, Data param2, boolean param3){   }

Donc le @CachePut, est nécessaire pour forcer la création ou la mise à jour d’une entrée dans le cache, sans attendre son expiration.
Le seul cas où la méthode n’est pas exécutée, c’est quand vous fournissez l’option condition de @CachePut, et votre condition est évaluée a false.

Activation de cache

Il est important de noter que les annotations de cache ne déclenchent automatiquement pas leurs exécutions.
Pour activer le support de cache dans un projet Spring, on commence par activer le traitement des annotations @Cacheable via la balise annotation-driven du namespace cache :

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:cache="http://www.springframework.org/schema/cache"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
       http://www.springframework.org/schema/cache
       http://www.springframework.org/schema/cache/spring-cache.xsd
       http://www.springframework.org/schema/context">
<cache:annotation-driven />

 
Notez qu’il suffit de supprimer cette balise pour désactiver le cache.
On peut aussi activer le support de cache par annotation en ajoutant @enableCaching dans l’un de nos classes de configuration (@Configuration classes) :

@Configuration
@EnableCaching
public class AppConfig {   }

Contraintes techniques

Choix de l’implémentation

Spring offre deux implémentations différentes du stockage de cache :

Pour les utiliser, il faut simplement déclarer un CacheManager approprié et une entité qui contrôle et gère les caches.

Implémentation cache de base ConcurrentHashMap de Java

Il faut déclarer les gestionnaires de caches SimpleCacheManager dans le contexte de l’application.

<bean id="cacheManager"
class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="dataCache"/>
</set>
</property>
</bean>

 
Chaque gestionnaire nécessite un nom (name) unique afin de l’identifier par annotation.
On peut déclarer plusieurs caches gérés par un seul manager SimpleCacheManager
Cette implémentation est basique, elle n’as pas besoin d’une bibliothèque supplémentaire, mais elle n’est pas trop prévue pour des grosses charges qui nécessitent de paramétrages supplémentaires.

Implémentation ehcache

Cette implémentation utilise ehcache, elle est beaucoup plus puissante et flexible.et elle permet un paramétrage avancé du cache de l’application.
L’implémentation ehcache est localisée sous le package org.springframework.cache.ehcache. Pour l’utiliser on doit déclarer le CacheManager approprié.

<bean id="cacheManager"
class="org.springframework.cache.ehcache.EhCacheCacheManager">
 <property name="cacheManager" ref="ehcache"/>
</bean>
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
 <property name="configLocation" value="classpath:ehcache.xml"/>
 <property name="shared" value="true"/>
</bean>

 
Le fichier ehcache.xml est le fichier de paramétrage des caches de l’application :

<ehcache xsi:noNamespaceSchemaLocation="ehcache.xsd"
   updateCheck="true"
   monitoring="autodetect"
   dynamicConfig="true"
   maxBytesLocalHeap="150M">
   <diskStore path="java.io.tmpdir"/>
   <defaultCache eternal="false"
     maxElementsInMemory="100"
     overflowToDisk="false"/>
   <cache name="dataCache"
     eternal="false"
     timeToIdleSeconds="300"
     maxBytesLocalHeap="30M"
     timeToLiveSeconds="300"
     overflowToDisk="true"
     diskPersistent="false"
     diskExpiryThreadIntervalSeconds="120"
     memoryStoreEvictionPolicy="LRU"/>
   </ehcache>

 
En utilisant l’ehcache, on peut définir plusieurs caches avec différents paramètres d’une manière très simple

Pour résumer avec une simple formule mathématique :

 expirationTime = Math.min((creationTime + timeToLive),(mostRecentTime + timeToIdle))

Conclusion

La gestion de cache est une problématique critique pour les applications Web, ainsi que la définition d’un système de cache applicatif est généralement considérée comme relativement complexe à mettre en œuvre par les développeurs, pour cela Spring a proposé un système générique de définition de cache complètement transparent, et simple.
Une démo est disponible sous : https://github.com/Zenika/Blogs/tree/master/20140527demoSpringCache

Quitter la version mobile