Caféine est une réimplémentation du cache Guava utilisant Java 8. Elle offre une expérience considérablement améliorée par rapport au cache de Guava et à ConcurrentLinkedHashMap. C'est un bibliothèque de cache hautes performances, considérée comme le cadre de cache optimal pour Java 8.
Le Cache Guava
Le cache Guava de Google est une excellente solutino de cache local qui propose des méthodes d'éviction basées sur la capacité, le temps et les références.
Les Capacités de Caféine
Le stockage des données en cache dans la JVM permet d'améliorer considérablement les performances des applications.
Pourquoi Utiliser un Cache Local ?
Par rapport aux opérations d'E/S, les caches locaux sont plus rapides et plus efficaces. Par rapport à Redis, qui est une excellente implémentation de cache distribué, les caches locaux évitent les limitations liées au réseau.
Inconvénients des Caches Locaux
En cas de redémarrage de nœud, le cache est perdu. Le stockage est limité par la JVM. Dans un cluster, les différents nœuds peuvent avoir des caches incohérents.
Quand Utiliser un Cache Local ?
Quand les mêmes résultats sont accédés de manière répétée. Quand on est prêt à sacrifier de l'espace mémoire pour gagner en vitesse. Quand la quantité totale de données en cache ne dépasse pas la capacité de la mémoire.
Comparaison des Caches Principaux
L'algorithme d'éviction de Caféine est plus avancé et bénéficie du support de Spring Cache (les nouvelles verisons de Spring Cache ne supportent plus Guava Cache).
Intégration avec Spring 2.x
Étape 1 : Ajout des Dépendances
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.9.2</version>
</dependency>
Étape 2 : Création de CacheConfig.java
@Configuration
public class ConfigurationCache {
@Bean
public Cache<String, Object> cacheCafeine() {
return Caffeine.newBuilder()
// Définit l'expiration après un temps fixe depuis la dernière écriture ou accès
.expireAfterWrite(60, TimeUnit.SECONDS)
// Taille initiale de l'espace cache
.initialCapacity(100)
// Nombre maximum d'entrées dans le cache
.maximumSize(1000)
.build();
}
}
Étape 3 : Injection
@Autowired
Cache<String, Object> cacheCafeine;
Étape 4 : Utilisation
1. Ajout au cache
cacheCafeine.put("cle","valeur");
2. Récupération depuis le cache
Object objet = cacheCafeine.getIfPresent("cle");
3. Récupération avec chargement si absent
Object o = cacheCafeine.get("cle", k -> chargerDonnee("cle"));
4. Stockage d'une entité
cacheCafeine.put("utilisateur", new DonneeUtilisateur(3, "chat"));
Object obj2 = cacheCafeine.getIfPresent("utilisateur");
DonneeUtilisateur ui2 = (DonneeUtilisateur)obj2;
5. Stockage d'une liste de chaînes
List<String> listeChaines = new ArrayList<>();
listeChaines.add("a");
listeChaines.add("b");
listeChaines.add("c");
listeChaines.add("d");
cacheCafeine.put("listeChaines", listeChaines);
Object listeCachee = cacheCafeine.getIfPresent("listeChaines");
List<String> listeChaines2 = (List<String>)listeCachee;
6. Stokcage d'une liste d'utilisateurs
List<DonneeUtilisateur> listeObjets = new ArrayList<>();
listeObjets.add(new DonneeUtilisateur(3, "chat"));
listeObjets.add(new DonneeUtilisateur(34, "chat4"));
cacheCafeine.put("listeObjets", listeObjets);
Object listeObjetsCachee = cacheCafeine.getIfPresent("listeObjets");
List<DonneeUtilisateur> listeObjets2 = (List<DonneeUtilisateur>)listeObjetsCachee;
7. Stockage d'une carte de listes
Map<String,List<DonneeUtilisateur>> carte = new HashMap<>();
List<DonneeUtilisateur> premiereListe = new ArrayList<>();
premiereListe.add(new DonneeUtilisateur(55, "oiseau"));
carte.put("11", premiereListe);
List<DonneeUtilisateur> deuxiemeListe = new ArrayList<>();
deuxiemeListe.add(new DonneeUtilisateur(55, "oiseau"));
deuxiemeListe.add(new DonneeUtilisateur(56, "chat"));
carte.put("12", deuxiemeListe);
cacheCafeine.put("carte", carte);
Object carteCachee = cacheCafeine.getIfPresent("carte");
Map<String,List<DonneeUtilisateur>> carte2 = (Map<String,List<DonneeUtilisateur>>)carteCachee;
Code Connexe
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class DonneeUtilisateur {
public DonneeUtilisateur(){}
public DonneeUtilisateur(int id ,String nom){
this.id = id;
this.nom = nom;
}
private Integer id;
private String nom;
private String sexe;
private Integer age;
}
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Caféine est une réimplémentation du cache Guava utilisant Java 8
*
* Dans Spring Boot 2.0, il remplacera Guava. Si Caféine est détecté,
* CaffeineCacheManager sera automatiquement configuré.
*
* Base de données + Redis + Cache Local = Stockage efficace, accès efficace
*
*
* Quand l'utiliser ?
* Prêt à sacrifier de l'espace mémoire pour gagner en vitesse
* On s'attend à ce que certaines clés soient interrogées plusieurs fois
* La quantité totale de données en cache ne dépassera pas la capacité mémoire
*
*
* Que peut-il faire ?
* Définir la capacité du cache
* Définir un délai d'expiration
* Fournir des écouteurs de suppression
* Fournir des chargeurs de cache
* Construire un cache
*
*
* Pour Spring 2.x, il suffit d'ajouter
* <dependency>
* <groupId>com.github.ben-manes.caffeine</groupId>
* <artifactId>caffeine</artifactId>
* </dependency>
*
*
* initialCapacity=[entier] : Taille initiale de l'espace cache
* maximumSize=[long] : Nombre maximum d'entrées dans le cache
* maximumWeight=[long] : Poids maximum du cache
* expireAfterAccess=[durée] : Expire après un temps fixe depuis la dernière écriture ou accès
* expireAfterWrite=[durée] : Expire après un temps fixe depuis la dernière écriture
* refreshAfterWrite=[durée] : Rafraîchit le cache après un intervalle de temps fixe
* weakKeys : Active les références faibles pour les clés
* weakValues : Active les références faibles pour les valeurs
* softValues : Active les références douces pour les valeurs
* recordStats : Active les statistiques
* Remarque :
* Quand expireAfterWrite et expireAfterAccess coexistent, c'est expireAfterWrite qui prévaut.
* maximumSize et maximumWeight ne peuvent pas être utilisés simultanément
* weakValues et softValues ne peuvent pas être utilisés simultanément
*
*/
@Configuration
public class ConfigurationCache {
@Bean
public Cache<String, Object> cacheCafeine() {
return Caffeine.newBuilder()
// Définit l'expiration après un temps fixe depuis la dernière écriture
.expireAfterWrite(60, TimeUnit.SECONDS)
// Taille initiale de l'espace cache
.initialCapacity(100)
// Nombre maximum d'entrées dans le cache
.maximumSize(1000)
.build();
}
}
package com.example.tousdemo.mo.outil.cache.guava;
import com.github.benmanes.caffeine.cache.Cache;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/cache")
public class ControleurUtilisateur {
@Autowired
Cache<String, Object> cacheCafeine;
public Object chargerDonnee(String cle){
return "Grand prédateur";
}
public void tester() {
log.info("entrée dans tester");
Object obj = cacheCafeine.getIfPresent("cle");
log.info("cle est [" + obj + "].");
Object o = cacheCafeine.get("cle", k -> chargerDonnee("cle"));
cacheCafeine.put("cle", "valeur");
obj = cacheCafeine.getIfPresent("cle");
if(obj != null){
log.info("cle est [" + obj.toString() + "].");
}
// Test d'objet
cacheCafeine.put("utilisateur", new DonneeUtilisateur(3, "chat"));
Object obj2 = cacheCafeine.getIfPresent("utilisateur");
DonneeUtilisateur ui2 = (DonneeUtilisateur)obj2;
// Test de liste de chaînes
List<String> listeChaines = new ArrayList<>();
listeChaines.add("a");
listeChaines.add("b");
listeChaines.add("c");
listeChaines.add("d");
cacheCafeine.put("listeChaines", listeChaines);
Object listeCachee = cacheCafeine.getIfPresent("listeChaines");
List<String> listeChaines2 = (List<String>)listeCachee;
// Test de liste
List<DonneeUtilisateur> listeObjets = new ArrayList<>();
listeObjets.add(new DonneeUtilisateur(3, "chat"));
listeObjets.add(new DonneeUtilisateur(34, "chat4"));
cacheCafeine.put("listeObjets", listeObjets);
Object listeObjetsCachee = cacheCafeine.getIfPresent("listeObjets");
List<DonneeUtilisateur> listeObjets2 = (List<DonneeUtilisateur>)listeObjetsCachee;
// Test de carte
Map<String,List<DonneeUtilisateur>> carte = new HashMap<>();
List<DonneeUtilisateur> premiereListe = new ArrayList<>();
premiereListe.add(new DonneeUtilisateur(55, "oiseau"));
carte.put("11", premiereListe);
List<DonneeUtilisateur> deuxiemeListe = new ArrayList<>();
deuxiemeListe.add(new DonneeUtilisateur(55, "oiseau"));
deuxiemeListe.add(new DonneeUtilisateur(56, "chat"));
carte.put("12", deuxiemeListe);
cacheCafeine.put("carte", carte);
Object carteCachee = cacheCafeine.getIfPresent("carte");
Map<String,List<DonneeUtilisateur>> carte2 = (Map<String,List<DonneeUtilisateur>>)carteCachee;
log.info("sortie de tester");
}
}