Un guide de la mise en cache au printemps

1. L'abstraction du cache?

Dans cet article, nous allons montrer comment utiliser l'abstraction de la mise en cache dans Spring - et en général, améliorer les performances de votre système.

Nous activerons la mise en cache simple pour certains exemples de méthodes du monde réel et nous discuterons de la manière dont nous pouvons pratiquement améliorer les performances de ces appels grâce à une gestion intelligente du cache.

2. Premiers pas

L'abstraction principale de la mise en cache fournie par Spring réside dans le module spring-context . Ainsi, lorsque vous utilisez Maven, notre pom.xml doit contenir la dépendance suivante:

 org.springframework spring-context 5.2.8.RELEASE 

Fait intéressant, il existe un autre module nommé spring-context-support, qui se trouve au-dessus du module spring-context et fournit quelques CacheManagers supplémentaires soutenus par EhCache ou Caffeine. Si vous allez les utiliser comme stockage de cache, utilisez à la place le module spring-context-support :

 org.springframework spring-context-support 5.2.8.RELEASE 

Puisque le module spring-context-support dépend de manière transitoire du module spring-context , il n'est pas nécessaire de faire une déclaration de dépendance distincte pour le spring-context.

2.1. Botte de printemps

Si vous êtes un utilisateur de Spring Boot, utilisez le package de démarrage spring-boot-starter-cache pour ajouter facilement les dépendances de mise en cache:

 org.springframework.boot spring-boot-starter-cache 2.3.3.RELEASE 

Sous le capot, le démarreur amène le module ressort-contexte-support .

3. Activer la mise en cache

Pour activer la mise en cache, Spring fait bon usage des annotations, tout comme l'activation de toute autre fonctionnalité de niveau de configuration dans le framework.

La fonction de mise en cache peut être activée de manière déclarative en ajoutant simplement l' annotation @EnableCaching à l'une des classes de configuration:

@Configuration @EnableCaching public class CachingConfig { @Bean public CacheManager cacheManager() { return new ConcurrentMapCacheManager("addresses"); } }

Vous pouvez bien sûr également activer la gestion du cache avec la configuration XML :

Remarque: après avoir activé la mise en cache - pour la configuration minimale - nous devons enregistrer un cacheManager .

3.1. Utilisation de Spring Boot

Lors de l'utilisation de Spring Boot, la simple présence du package de démarrage sur le chemin de classe à côté de l' annotation EnableCaching enregistrerait le même ConcurrentMapCacheManager. Il n'y a donc pas besoin d'une déclaration de haricot distincte.

Nous pouvons également personnaliser le CacheManager configuré automatiquement à l' aide d'un ou plusieurs beans CacheManagerCustomizer :

@Component public class SimpleCacheCustomizer implements CacheManagerCustomizer { @Override public void customize(ConcurrentMapCacheManager cacheManager) { cacheManager.setCacheNames(asList("users", "transactions")); } }

La configuration automatique de CacheAutoConfiguration récupère ces personnalisateurs et les applique au CacheManager actuel avant son initialisation complète.

4. Utiliser la mise en cache avec des annotations

Une fois que nous avons activé la mise en cache, l'étape suivante consiste à lier le comportement de mise en cache aux méthodes avec des annotations déclaratives.

4.1. @ Cacheable

Le moyen le plus simple d'activer le comportement de mise en cache pour une méthode est de la délimiter avec @Cacheable et de le paramétrer avec le nom du cache où les résultats seraient stockés:

@Cacheable("addresses") public String getAddress(Customer customer) {...} 

L' appel getAddress () vérifiera d'abord les adresses de cache avant d'appeler réellement la méthode, puis de mettre en cache le résultat.

Alors que dans la plupart des cas, un cache suffit, le framework Spring prend également en charge plusieurs caches à transmettre en tant que paramètres:

@Cacheable({"addresses", "directory"}) public String getAddress(Customer customer) {...}

Dans ce cas, si l'un des caches contient le résultat requis, le résultat est renvoyé et la méthode n'est pas appelée.

4.2. @ CacheEvict

Maintenant, quel serait le problème de rendre toutes les méthodes @Cacheable ?

Le problème est la taille - nous ne voulons pas remplir le cache avec des valeurs dont nous n'avons pas souvent besoin . Les caches peuvent devenir assez volumineux, assez rapides, et nous pourrions conserver beaucoup de données périmées ou inutilisées.

L' annotation @CacheEvict est utilisée pour indiquer la suppression d'une ou plusieurs / toutes les valeurs - afin que les nouvelles valeurs puissent être chargées à nouveau dans le cache:

@CacheEvict(value="addresses", allEntries=true) public String getAddress(Customer customer) {...}

Ici, nous utilisons le paramètre supplémentaire allEntries en conjonction avec le cache à vider - pour effacer toutes les entrées dans les adresses de cache et le préparer aux nouvelles données.

4.3. @ CachePut

While @CacheEvict reduces the overhead of looking up entries in a large cache by removing stale and unused entries, ideally, you want to avoid evicting too much data out of the cache.

Instead, you'd want to selectively and intelligently update the entries whenever they're altered.

With the @CachePut annotation, you can update the content of the cache without interfering the method execution. That is, the method would always be executed and the result cached.

@CachePut(value="addresses") public String getAddress(Customer customer) {...}

The difference between @Cacheable and @CachePut is that @Cacheable will skip running the method, whereas @CachePut will actually run the method and then put its results in the cache.

4.4. @Caching

What if you want to use multiple annotations of the same type for caching a method. Look at the incorrect example below:

@CacheEvict("addresses") @CacheEvict(value="directory", key=customer.name) public String getAddress(Customer customer) {...}

The above code would fail to compile since Java does not allow multiple annotations of the same type to be declared for a given method.

The workaround to the above issue would be:

@Caching(evict = { @CacheEvict("addresses"), @CacheEvict(value="directory", key="#customer.name") }) public String getAddress(Customer customer) {...}

As shown in the code snippet above, you can group multiple caching annotations with @Caching, and use it to implement your own customized caching logic.

4.5. @CacheConfig

With the @CacheConfig annotation, you can streamline some of the cache configuration into a single place – at the class level – so that you don't have to declare things multiple times:

@CacheConfig(cacheNames={"addresses"}) public class CustomerDataService { @Cacheable public String getAddress(Customer customer) {...}

5. Conditional Caching

Sometimes, caching might not work well for a method in all situations.

For example – reusing our example from the @CachePut annotation – this will both execute the method as well as cache the results each and every time:

@CachePut(value="addresses") public String getAddress(Customer customer) {...} 

5.1. Condition Parameter

Now – if we want more control over when the annotation is active – @CachePut can be parametrized with a condition parameter that takes a SpEL expression to ensure that the results are cached based on evaluating that expression:

@CachePut(value="addresses", condition="#customer.name=='Tom'") public String getAddress(Customer customer) {...}

5.2. Unless Parameter

We can also control the caching based on the output of the method rather than the input – via the unless parameter:

@CachePut(value="addresses", unless="#result.length()<64") public String getAddress(Customer customer) {...}

The above annotation would cache addresses unless they are shorter than 64 characters.

It's important to know that the condition and unless parameters can be used in conjunction with all the caching annotations.

Ce type de mise en cache conditionnelle peut s'avérer très utile pour gérer des résultats volumineux et personnaliser le comportement en fonction des paramètres d'entrée au lieu d'appliquer un comportement générique à toutes les opérations.

6. Mise en cache déclarative basée sur XML

Si vous n'avez pas accès au code source de votre application ou si vous souhaitez injecter le comportement de mise en cache en externe, vous pouvez également utiliser la mise en cache déclarative basée sur XML.

Voici notre configuration XML:

7. La mise en cache basée sur Java

Et voici la configuration Java équivalente:

@Configuration @EnableCaching public class CachingConfig { @Bean public CacheManager cacheManager() { SimpleCacheManager cacheManager = new SimpleCacheManager(); cacheManager.setCaches(Arrays.asList( new ConcurrentMapCache("directory"), new ConcurrentMapCache("addresses"))); return cacheManager; } }

Et voici notre CustomerDataService :

@Component public class CustomerDataService { @Cacheable(value = "addresses", key = "#customer.name") public String getAddress(Customer customer) { return customer.getAddress(); } }

8. Résumé

Dans cet article, nous avons discuté des bases de la mise en cache au printemps et de la façon de faire bon usage de cette abstraction avec des annotations.

L'implémentation complète de cet article se trouve dans le projet GitHub.