Contactez-nous

Configuration d'un gestionnaire de cache (`CacheManager`)

Apprenez à configurer et personnaliser le CacheManager dans Spring Boot pour différents fournisseurs de cache (Caffeine, Redis, EhCache...) et à définir des options de cache spécifiques.

Le rôle central du CacheManager

Dans l'abstraction de cache de Spring, le org.springframework.cache.CacheManager est l'interface pivot. Il sert de pont entre l'infrastructure de mise en cache de Spring (activée par @EnableCaching et utilisant des annotations comme @Cacheable) et l'implémentation de cache concrète que vous avez choisie (Caffeine, EhCache, Redis, Hazelcast, une simple Map, etc.).

Le rôle principal du CacheManager est de fournir des instances de org.springframework.cache.Cache sur demande. Chaque instance de Cache représente un cache nommé (par exemple, "produits", "utilisateurs") et expose les opérations de base nécessaires : récupérer une valeur par clé (get), ajouter ou mettre à jour une valeur (put), et supprimer une valeur (evict). Les proxies AOP de Spring utilisent le CacheManager pour obtenir le Cache approprié (basé sur le nom spécifié dans l'annotation) et interagissent ensuite avec cet objet Cache pour appliquer la logique de mise en cache.

Pour que l'abstraction de cache de Spring fonctionne, il est donc indispensable qu'au moins un bean de type CacheManager soit défini et disponible dans le contexte d'application Spring.

L'auto-configuration de Spring Boot : la magie par défaut

Heureusement, Spring Boot simplifie grandement la configuration du CacheManager grâce à son mécanisme d'auto-configuration. Si vous avez activé le caching (@EnableCaching) et ajouté une dépendance vers un fournisseur de cache supporté dans votre projet, Spring Boot tentera de configurer automatiquement un CacheManager pour vous.

L'ordre de détection et de préférence est généralement le suivant (peut légèrement varier selon la version de Spring Boot) :

  1. JCache (si javax.cache.CachingProvider est présent et configuré)
  2. Caffeine (si com.github.ben-manes.caffeine:caffeine est présent)
  3. EhCache 2.x (si net.sf.ehcache:ehcache est présent et un fichier ehcache.xml est trouvé)
  4. Hazelcast (si com.hazelcast:hazelcast est présent et configuré)
  5. Infinispan (si org.infinispan:infinispan-spring-boot-starter-cache est présent)
  6. Couchbase (si com.couchbase.client:java-client et spring-boot-starter-data-couchbase sont présents)
  7. Redis (si org.springframework.boot:spring-boot-starter-data-redis est présent)
  8. Cache générique (si aucun des précédents n'est trouvé)
  9. Simple (basé sur ConcurrentHashMap, comme dernier recours si spring-boot-starter-cache est présent mais aucune autre implémentation)

Par exemple, si vous ajoutez spring-boot-starter-cache et la dépendance Caffeine à votre `pom.xml`, Spring Boot auto-configurera un CaffeineCacheManager sans que vous ayez à écrire de code de configuration Java spécifique. Les caches seront créés dynamiquement à la volée lorsqu'ils seront référencés pour la première fois dans une annotation (@Cacheable("monNouveauCache")), avec des paramètres par défaut.

Vous pouvez influencer légèrement l'auto-configuration via des propriétés dans application.properties/yml, notamment pour spécifier les noms des caches à pré-créer (spring.cache.cache-names) ou pour choisir explicitement le type de cache à utiliser si plusieurs fournisseurs sont présents (spring.cache.type=redis).

Personnalisation via les propriétés (exemple Redis)

Pour certains fournisseurs, l'auto-configuration peut être affinée via des propriétés spécifiques. Un bon exemple est Redis. Lorsque spring-boot-starter-data-redis est présent, un RedisCacheManager est auto-configuré. Vous pouvez personnaliser le comportement de ce cache via des propriétés spring.cache.redis.*.

Exemple de configuration dans application.properties pour Redis :

# Activer le cache Redis (souvent implicite si dépendance présente)
spring.cache.type=redis

# Durée de vie (Time To Live - TTL) par défaut pour toutes les entrées du cache
spring.cache.redis.time-to-live=60000 # 60 secondes (en millisecondes)

# Préfixe optionnel pour les clés de cache dans Redis
spring.cache.redis.key-prefix=monapp::cache::

# Utiliser le préfixe de clé (true par défaut)
spring.cache.redis.use-key-prefix=true

# Autoriser ou non le stockage de valeurs nulles (false par défaut)
spring.cache.redis.cache-null-values=true

# Configuration de la connexion Redis elle-même (si différente des valeurs par défaut localhost:6379)
spring.redis.host=redis.example.com
spring.redis.port=6379
# spring.redis.password=...

Ces propriétés permettent un contrôle de base sur le comportement du cache Redis sans nécessiter de configuration Java. Cependant, pour des personnalisations plus avancées (TTL différents par cache, configuration de la sérialisation, etc.), une configuration Java explicite sera nécessaire.

Configuration Java explicite du CacheManager

Lorsque l'auto-configuration ne suffit pas ou que vous souhaitez un contrôle total, vous pouvez définir votre propre bean CacheManager dans une classe de configuration @Configuration.

Exemple avec Caffeine (pour définir des spécifications de cache) :

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
@EnableCaching
public class CustomCacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager cacheManager = new CaffeineCacheManager("utilisateurs", "produits");
        // Configuration par défaut pour les caches créés dynamiquement
        // cacheManager.setCaffeine(caffeineCacheBuilder());

        // Configuration spécifique pour le cache "utilisateurs"
        cacheManager.registerCustomCache("utilisateurs", 
            Caffeine.newBuilder()
                .maximumSize(100)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .build());

        // Configuration spécifique pour le cache "produits"
        cacheManager.registerCustomCache("produits", 
            Caffeine.newBuilder()
                .maximumSize(500)
                .expireAfterAccess(30, TimeUnit.MINUTES)
                .weakKeys() // Exemple d'option spécifique Caffeine
                .build());
        
        // Si vous voulez permettre la création dynamique d'autres caches avec des réglages par défaut
        // cacheManager.setCacheSpecification("maximumSize=200,expireAfterWrite=5m"); // Alternative via String spec

        return cacheManager;
    }

    // Helper pour la config par défaut (si nécessaire)
    // Caffeine caffeineCacheBuilder() {
    //     return Caffeine.newBuilder()
    //         .initialCapacity(100)
    //         .maximumSize(500)
    //         .expireAfterWrite(10, TimeUnit.MINUTES)
    //         .recordStats();
    // }
}

Dans cet exemple, nous créons explicitement un CaffeineCacheManager, enregistrons deux caches nommés ("utilisateurs", "produits") avec des configurations Caffeine spécifiques (taille maximale, politique d'expiration différente).

Exemple avec Redis (pour définir des TTL par cache et configurer la sérialisation) :

import org.springframework.boot.autoconfigure.cache.RedisCacheManagerBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext.SerializationPair;

import java.time.Duration;

@Configuration
@EnableCaching
public class CustomRedisCacheConfig {

    // Configuration de sérialisation JSON (à utiliser pour les valeurs)
    private final SerializationPair jsonSerializer = 
        SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer());

    @Bean
    public RedisCacheConfiguration cacheConfiguration() {
        // Configuration par défaut pour les caches
        return RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(60)) // TTL par défaut de 60 minutes
                .disableCachingNullValues() // Ne pas cacher les valeurs nulles par défaut
                .prefixCacheNameWith("monapp::cache::") // Préfixe global
                .serializeValuesWith(jsonSerializer); // Utiliser JSON pour les valeurs
    }

    @Bean
    public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
        // Permet de personnaliser le CacheManager auto-configuré par Spring Boot
        // OU pour définir des configurations spécifiques par cache lors de la création manuelle du manager
        return (builder) -> builder
                .withCacheConfiguration("produits", // Cache spécifique "produits"
                        RedisCacheConfiguration.defaultCacheConfig()
                                .entryTtl(Duration.ofMinutes(10))
                                .prefixCacheNameWith("monapp::produits::") // Préfixe spécifique
                                .serializeValuesWith(jsonSerializer))
                .withCacheConfiguration("utilisateurs", // Cache spécifique "utilisateurs"
                        RedisCacheConfiguration.defaultCacheConfig()
                                .entryTtl(Duration.ofHours(1))
                                .serializeValuesWith(jsonSerializer));
    }

    // Note: Si vous définissez un bean RedisCacheConfiguration, Spring Boot l'utilisera
    // pour l'auto-configuration du CacheManager. Si vous définissez un
    // RedisCacheManagerBuilderCustomizer, il personnalisera ce CacheManager auto-configuré.
    // Si vous voulez un contrôle total, définissez votre propre bean CacheManager
    // en utilisant RedisCacheConfiguration et RedisConnectionFactory.
}

Cette approche avec RedisCacheManagerBuilderCustomizer est souvent préférée car elle permet de bénéficier de l'auto-configuration de la RedisConnectionFactory tout en personnalisant finement les aspects du cache (TTL, sérialisation par cache).

Gestion de plusieurs CacheManagers

Dans certains cas avancés, vous pourriez avoir besoin d'utiliser plusieurs fournisseurs de cache simultanément (par exemple, un cache en mémoire rapide pour certaines données et Redis pour d'autres). Spring permet de définir plusieurs beans CacheManager.

Cependant, si plusieurs CacheManager existent, Spring ne sait pas lequel utiliser par défaut. Vous devez alors explicitement désigner l'un d'eux comme étant le principal avec l'annotation @Primary sur sa définition de bean.

Pour utiliser un CacheManager non primaire avec les annotations de cache, vous devez spécifier son nom de bean dans l'attribut cacheManager de l'annotation :

@Service
public class MonService {

    // Utilisera le CacheManager @Primary par défaut
    @Cacheable("cachePrimaire")
    public String methodeCacheePrimaire(String id) {
        // ...
        return "donnees primaires";
    }

    // Spécifie explicitement d'utiliser le CacheManager nommé "redisCacheManager"
    @Cacheable(value = "cacheSecondaire", cacheManager = "redisCacheManager")
    public String methodeCacheeSecondaire(String id) {
        // ...
        return "donnees secondaires";
    }
}

Cela nécessite que vous ayez défini des beans CacheManager avec les noms correspondants (par exemple, via @Bean(name = "redisCacheManager")).

Conclusion : Maîtriser le CacheManager pour un caching efficace

Le CacheManager est le coeur de l'abstraction de cache de Spring. Bien que Spring Boot fasse un excellent travail d'auto-configuration dans la plupart des cas, comprendre le rôle du CacheManager et savoir comment le personnaliser est essentiel pour optimiser la stratégie de mise en cache de votre application.

Que ce soit via les propriétés de configuration pour des ajustements simples ou via une configuration Java explicite pour un contrôle fin (définition des caches nommés, TTL spécifiques, politiques d'éviction, sérialisation), la maîtrise du CacheManager vous permet d'adapter précisément le comportement du cache à vos besoins, garantissant ainsi des gains de performance optimaux tout en maintenant la clarté de votre code métier grâce à l'approche déclarative de Spring.