Una guía para el almacenamiento en caché en primavera

1. ¿La abstracción de la caché?

En este artículo, vamos a mostrar cómo usar la abstracción de almacenamiento en caché en Spring y, en general, mejorar el rendimiento de su sistema.

Habilitaremos el almacenamiento en caché simple para algunos ejemplos de métodos del mundo real y discutiremos cómo podemos mejorar prácticamente el rendimiento de estas llamadas mediante la administración de caché inteligente.

2. Comenzando

La abstracción de almacenamiento en caché principal proporcionada por Spring reside en el módulo de contexto de Spring . Entonces, al usar Maven, nuestro pom.xml debe contener la siguiente dependencia:

 org.springframework spring-context 5.2.8.RELEASE 

Curiosamente, hay otro módulo llamado spring-context-support, que se encuentra en la parte superior del módulo spring-context y proporciona algunos CacheManagers más respaldados por EhCache o Caffeine. Si va a usarlos como almacenamiento en caché, use el módulo spring-context-support en su lugar:

 org.springframework spring-context-support 5.2.8.RELEASE 

Dado que el módulo spring-context-support depende transitivamente del módulo spring-context , no hay necesidad de una declaración de dependencia separada para el spring-context.

2.1. Bota de primavera

Si es un usuario de Spring Boot, utilice el paquete de inicio spring-boot-starter-cache para agregar fácilmente las dependencias de almacenamiento en caché:

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

Debajo del capó, el motor de arranque trae el módulo de soporte de contexto de resorte .

3. Habilitar el almacenamiento en caché

Para habilitar el almacenamiento en caché, Spring hace un buen uso de las anotaciones, al igual que habilitar cualquier otra característica de nivel de configuración en el marco.

La función de almacenamiento en caché se puede habilitar mediante declaración simplemente agregando la anotación @EnableCaching a cualquiera de las clases de configuración:

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

Por supuesto, también puede habilitar la administración de caché con la configuración XML :

Nota: Después de habilitar el almacenamiento en caché, para la configuración mínima, debemos registrar un cacheManager .

3.1. Usando Spring Boot

Al usar Spring Boot, la mera presencia del paquete de inicio en la ruta de clase junto con la anotación EnableCaching registraría el mismo ConcurrentMapCacheManager. Entonces, no hay necesidad de una declaración de bean por separado.

Además, podemos personalizar el CacheManager autoconfigurado usando uno o más beans CacheManagerCustomizer :

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

La configuración automática de CacheAutoConfiguration recoge estos personalizadores y los aplica al CacheManager actual antes de su inicialización completa.

4. Utilice el almacenamiento en caché con anotaciones

Una vez que hemos habilitado el almacenamiento en caché, el siguiente paso es vincular el comportamiento del almacenamiento en caché a los métodos con anotaciones declarativas.

4.1. @ Cacheable

La forma más sencilla de habilitar el comportamiento de almacenamiento en caché para un método es demarcarlo con @Cacheable y parametrizarlo con el nombre de la caché donde se almacenarían los resultados:

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

La llamada getAddress () primero verificará las direcciones de caché antes de invocar el método y luego almacenar en caché el resultado.

Si bien en la mayoría de los casos, una caché es suficiente, el marco Spring también admite múltiples cachés para pasar como parámetros:

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

En este caso, si alguno de los cachés contiene el resultado requerido, se devuelve el resultado y no se invoca el método.

4.2. @ CacheEvict

Ahora, ¿cuál sería el problema de hacer que todos los métodos sean @Cacheable ?

El problema es el tamaño: no queremos llenar el caché con valores que no necesitamos a menudo . Los cachés pueden crecer bastante, bastante rápido, y podríamos retener una gran cantidad de datos obsoletos o no utilizados.

La anotación @CacheEvict se usa para indicar la eliminación de uno o más / todos los valores, de modo que los valores nuevos se puedan cargar nuevamente en la caché:

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

Aquí, estamos usando el parámetro adicional allEntries junto con el caché que se va a vaciar, para borrar todas las entradas en las direcciones del caché y prepararlo para nuevos datos.

4.3. @ CachePut

Si bien @CacheEvict reduce la sobrecarga de buscar entradas en un caché grande al eliminar las entradas obsoletas y no utilizadas, lo ideal es evitar desalojar demasiados datos del caché .

En su lugar, querrá actualizar de manera selectiva e inteligente las entradas cada vez que se modifiquen.

Con la anotación @CachePut , puede actualizar el contenido de la caché sin interferir en la ejecución del método. Es decir, el método siempre se ejecutará y el resultado se guardará en caché.

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

Este tipo de almacenamiento en caché condicional puede resultar bastante útil para administrar grandes resultados y personalizar el comportamiento en función de los parámetros de entrada en lugar de imponer un comportamiento genérico a todas las operaciones.

6. Almacenamiento en caché declarativo basado en XML

En caso de que no tenga acceso al código fuente de su aplicación o desee inyectar el comportamiento de almacenamiento en caché externamente, también puede utilizar el almacenamiento en caché declarativo basado en XML.

Aquí está nuestra configuración XML:

7. El almacenamiento en caché basado en Java

Y aquí está la configuración de 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; } }

Y aquí está nuestro CustomerDataService :

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

8. Resumen

En este artículo, discutimos los conceptos básicos del almacenamiento en caché en Spring y cómo hacer un buen uso de esa abstracción con anotaciones.

La implementación completa de este artículo se puede encontrar en el proyecto GitHub.