Una guida al caching in primavera

1. L'astrazione della cache?

In questo articolo, mostreremo come utilizzare l'astrazione della cache in primavera e, in generale, come migliorare le prestazioni del tuo sistema.

Abiliteremo la semplice memorizzazione nella cache per alcuni esempi di metodi del mondo reale e discuteremo come possiamo praticamente migliorare le prestazioni di queste chiamate attraverso la gestione intelligente della cache.

2. Introduzione

L'astrazione di caching principale fornita da Spring risiede nel modulo spring-context . Quindi, quando si utilizza Maven, il nostro pom.xml dovrebbe contenere la seguente dipendenza:

 org.springframework spring-context 5.2.8.RELEASE 

È interessante notare che esiste un altro modulo denominato spring-context-support, che si trova sopra il modulo spring-context e fornisce alcuni altri CacheManager supportati da artisti del calibro di EhCache o Caffeine. Se hai intenzione di usarli come memoria cache, usa invece il modulo spring-context-support :

 org.springframework spring-context-support 5.2.8.RELEASE 

Poiché il modulo spring-context-support dipende transitivamente dal modulo spring-context , non è necessaria una dichiarazione di dipendenza separata per spring-context.

2.1. Spring Boot

Se sei un utente Spring Boot, utilizza il pacchetto di avvio spring-boot-starter-cache per aggiungere facilmente le dipendenze di memorizzazione nella cache:

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

Sotto il cofano, lo starter porta il modulo di supporto del contesto primaverile .

3. Abilita la memorizzazione nella cache

Per abilitare il caching, Spring fa buon uso delle annotazioni, proprio come abilitare qualsiasi altra funzionalità a livello di configurazione nel framework.

La funzionalità di memorizzazione nella cache può essere abilitata in modo dichiarativo semplicemente aggiungendo l' annotazione @EnableCaching a una qualsiasi delle classi di configurazione:

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

Ovviamente puoi abilitare la gestione della cache anche con la configurazione XML :

Nota: dopo aver abilitato la memorizzazione nella cache, per la configurazione minima, è necessario registrare un cacheManager .

3.1. Utilizzo di Spring Boot

Quando si utilizza Spring Boot, la semplice presenza del pacchetto iniziale sul classpath insieme all'annotazione EnableCaching registrerebbe lo stesso ConcurrentMapCacheManager. Quindi, non è necessaria una dichiarazione di bean separata.

Inoltre, possiamo personalizzare il CacheManager configurato automaticamente utilizzando uno o più bean CacheManagerCustomizer :

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

La configurazione automatica di CacheAutoConfiguration preleva questi personalizzatori e li applica al CacheManager corrente prima della sua inizializzazione completa.

4. Utilizzare la memorizzazione nella cache con annotazioni

Dopo aver abilitato la memorizzazione nella cache, il passaggio successivo è associare il comportamento di memorizzazione nella cache ai metodi con annotazioni dichiarative.

4.1. @ Cacheable

Il modo più semplice per abilitare il comportamento di memorizzazione nella cache per un metodo è demarcarlo con @Cacheable e parametrizzarlo con il nome della cache in cui verranno memorizzati i risultati:

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

La chiamata getAddress () controllerà prima gli indirizzi della cache prima di invocare effettivamente il metodo e quindi memorizzare nella cache il risultato.

Sebbene nella maggior parte dei casi una cache sia sufficiente, il framework Spring supporta anche più cache da passare come parametri:

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

In questo caso, se una qualsiasi delle cache contiene il risultato richiesto, il risultato viene restituito e il metodo non viene richiamato.

4.2. @ CacheEvict

Ora, quale sarebbe il problema se tutti i metodi fossero @Cacheable ?

Il problema è la dimensione: non vogliamo popolare la cache con valori di cui non abbiamo bisogno spesso . Le cache possono crescere abbastanza grandi, abbastanza velocemente e potremmo trattenere molti dati obsoleti o inutilizzati.

L' annotazione @CacheEvict viene utilizzata per indicare la rimozione di uno o più / tutti i valori, in modo che i nuovi valori possano essere caricati nuovamente nella cache:

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

Qui, stiamo usando il parametro aggiuntivo allEntries insieme alla cache da svuotare - per cancellare tutte le voci negli indirizzi della cache e prepararla per nuovi dati.

4.3. @ CachePut

Mentre @CacheEvict riduce il sovraccarico della ricerca di voci in una cache di grandi dimensioni rimuovendo voci obsolete e inutilizzate, idealmente si desidera evitare di espellere troppi dati dalla cache .

Invece, ti consigliamo di aggiornare in modo selettivo e intelligente le voci ogni volta che vengono modificate.

Con l' annotazione @CachePut , puoi aggiornare il contenuto della cache senza interferire con l'esecuzione del metodo. Cioè, il metodo verrebbe sempre eseguito e il risultato memorizzato nella cache.

@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.

Questo tipo di memorizzazione nella cache condizionale può rivelarsi molto utile per la gestione di risultati di grandi dimensioni e la personalizzazione del comportamento in base ai parametri di input invece di imporre un comportamento generico a tutte le operazioni.

6. Memorizzazione nella cache basata su XML dichiarativa

Nel caso in cui non si abbia accesso al codice sorgente della propria applicazione o si desideri iniettare esternamente il comportamento di memorizzazione nella cache, è possibile utilizzare anche la memorizzazione nella cache basata su XML dichiarativa.

Ecco la nostra configurazione XML:

7. Il caching basato su Java

Ed ecco la configurazione Java equivalente:

@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; } }

Ed ecco il nostro CustomerDataService :

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

8. Riepilogo

In questo articolo, abbiamo discusso le basi della memorizzazione nella cache in primavera e come fare un buon uso di tale astrazione con le annotazioni.

L'implementazione completa di questo articolo può essere trovata nel progetto GitHub.