Almacenamiento en caché en Spring con anotaciones EhCache
Publicado: 2022-03-11EhCache es un caché de Java puro ampliamente utilizado que se puede integrar fácilmente con los marcos de Java más populares, como Spring e Hibernate. A menudo se considera que es la opción más conveniente para las aplicaciones Java, ya que se puede integrar fácilmente en los proyectos. En particular:
- Se puede configurar simplemente incluyendo el JAR en su proyecto. No se requieren pasos de instalación adicionales.
- Se ejecuta en el mismo proceso que la aplicación, por lo que es rápido. No se requiere ningún servicio adicional para funcionar.
En resumen, EhCache es una excelente opción para cualquier aplicación de Java puro.
Además, EhCache Spring Annotations permite una integración perfecta en cualquier aplicación Spring simplemente agregando anotaciones a los métodos almacenables en caché, sin modificar las implementaciones del método.
Si bien EhCache proporciona API enriquecidas y sencillas para manipular el caché mediante programación, este artículo se enfoca principalmente en impulsar sus aplicaciones Spring de una manera menos intrusiva con EhCache Spring Annotations. Configuraremos un proyecto Spring MVC e implementaremos un servicio web RESTful en Tomcat. Luego, EhCache se integrará al servicio web.
Descripción del proyecto
Demostraremos las anotaciones de EhCache en el contexto de un proyecto de ejemplo. Configuraremos un servicio web basado en Spring MVC alojado en un servidor Tomcat 8.
Desarrollé el proyecto en Eclipse, el cual se puede instalar siguiendo las instrucciones aquí.
La última versión estable de Tomcat, Tomcat 8, se puede descargar aquí.
Por supuesto, estas plataformas específicas no son un requisito para EhCache; siempre puede elegir su IDE y servidor favoritos.
El JAR de anotaciones de EhCache Spring está disponible aquí. Como podemos ver, hay dos JAR para cada versión: uno con dependencias y otro sin ellas. El que tiene dependencias también incluye EhCache 2 y Spring 3, que son necesarios para que funcionen las anotaciones de EhCache. Es más fácil de configurar si descargamos el que tiene dependencias y lo agregamos a nuestra ruta de compilación.
EhCache Spring Annotations también es compatible con Spring 4, aunque debe configurarse por separado. No está claro si el proyecto admitirá EhCache 3 en un futuro próximo. Para aquellos que usan, o tienen la intención de usar, EhCache 3, no se recomienda el enfoque de anotación que se analiza en este artículo.
Finalmente, usaremos Maven para administrar todo. Maven viene empaquetado con la mayoría de las instalaciones de Eclipse, pero también se puede obtener aquí. Las dependencias Spring MVC y EhCache Spring Annotations se pueden agregar con bastante facilidad, como se muestra más adelante en este artículo.
Configuración del proyecto
Si nunca antes ha configurado un proyecto Spring, también puede encontrar informativa la publicación de Stefan Varga sobre el tema.
Para esta demostración, configuraremos un proyecto básico utilizando el arquetipo Maven maven-archetype-webapp . La estructura general del archivo se verá así:
Cree un directorio, src/main/java , con tres paquetes: com.toptal.blog , com.toptal.blog.cache y com.toptal.blog.service . Nuestra fuente de aplicación irá en estos paquetes, como se describe más adelante.
Definamos un servlet de Tomcat llamado "springrest" en web.xml :
<web-app> ... <servlet> <servlet-name>springrest</servlet-name> <servlet-class> org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springrest</servlet-name> <url-pattern>/*</url-pattern> </servlet-mapping> </web-app> A menos que se especifique explícitamente lo contrario, Spring MVC DispatcherServlet buscará un archivo de configuración XML llamado {servlet-name}-servlet.xml en el directorio WEB-INF . Vamos a crear un archivo de configuración llamado springrest-servlet.xml . Para habilitar los métodos del controlador de procesos de Spring anotados con @RequestMapping , simplemente agreguemos <mvc:annotation-driven /> a este archivo. Además, definamos el paquete base para que Spring escanee y registre beans automáticamente agregando <context:component-scan base-package="com.toptal.blog" /> . La configuración springrest-servlet.xml se convierte en:
<beans ... > <mvc:annotation-driven /> <context:component-scan base-package="com.toptal.blog" /> </beans>Un servicio web RESTful simple
Ahora que nuestro proyecto está configurado correctamente, implementemos una API simple de "servicio de mensajes". En nuestro paquete base, project.toptal.blog , agregaremos SpringRestControllerWithEhCache.java , con un método GET para obtener un mensaje por ID y un método POST para configurar un mensaje por ID:
@RestController @RequestMapping( "/" ) public class SpringRestControllerWithEhCache { @Autowired MessageService messageService; @RequestMapping( value = "/message/{id}", method = RequestMethod.GET ) public String getMessage( @PathVariable Integer id ) { String message = messageService.getMessage( id ); System.out.println( "get message ["+message+"] at "+new Date() ); return message; } @RequestMapping( value = "/message/set/{id}/{message}", method = RequestMethod.POST ) public String setMessage( @PathVariable Integer id, @PathVariable String message ) { System.out.println( "set message ["+message+"] at "+new Date() ); messageService.setMessage( id, message ); return message; } } Definiremos la clase MessageService en com.toptal.blog.service . Accederá a los mensajes almacenados en nuestro Sistema de Registros (SOR). En una aplicación de producción, el SOR sería algo así como una base de datos relacional. Para simplificar, usaremos un HashMap :
@Service public class MessageService { private ConcurrentHashMap<Integer, String> messages = new ConcurrentHashMap<Integer, String>(); public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } public void setMessage( Integer id, String message ){ messages.put( id, message ); } } Ahora, si exportamos el proyecto como WAR y lo implementamos en Tomcat, deberíamos poder establecer un mensaje, por ejemplo, "test_message", para ID=1, creando una solicitud HTTP POST en http://localhost:8080/EhCacheExample/message/set/1/test_message . Entonces deberíamos poder recuperar "test_message" con una solicitud HTTP GET en http://localhost:8080/EhCacheExample/message/1 . Usé Insomnia como un cliente REST conveniente para hacer mi prueba.
Conecte las anotaciones de primavera de EhCache
Ahora hagamos que EhCache funcione para nosotros. Solo se necesitan unos pocos pasos rápidos para configurar nuestro proyecto para ejecutar EhCache correctamente.
Paso 1: actualice las dependencias para usar las anotaciones Spring de EhCache
Agregue la dependencia EhCache Spring Annotations en pom.xml de Maven:
<!-- ehcache --> <dependency> <groupId>com.googlecode.ehcache-spring-annotations</groupId> <artifactId>ehcache-spring-annotations</artifactId> <version>1.2.0</version> </dependency>Paso 2: configurar un administrador de caché personalizado
Spring tiene un administrador de caché EhCache incorporado, org.springframework.cache.ehcache.EhCacheManagerFactoryBean . Esto es adecuado para la mayoría de las situaciones de almacenamiento en caché, pero descubrí que definir un administrador de caché personalizado es útil porque me permite controlar el caché mediante programación o con anotaciones, usando el mismo administrador de caché. Este artículo se centra en las anotaciones, pero avancemos y definamos un administrador de caché personalizado para que estemos listos en caso de que lo necesitemos. Si prefiere quedarse con el administrador de caché predeterminado, puede omitir este paso.

Definiremos la nueva clase en com.toptal.blog.cache.CustomCacheManager :
public class CustomCacheManager extends net.sf.ehcache.CacheManager{ public CustomCacheManager(){ super(); } /* Add your own cache methods here. * * public void myCustomCacheMethod(){ * // your code here * } * */ } Habilítelo actualizando springrest-servlet.xml de la siguiente manera:
... <ehcache:annotation-driven cache-manager="customCacheManager" /> <bean class="com.toptal.blog.cache.CustomCacheManager" scope="singleton"></bean> ...Paso 3: Configurar EhCache
Finalmente, cree el archivo de configuración de ehcache.xml en el classpath. De forma predeterminada, Eclipse incluirá src/main/resources en el classpath y colocaremos el archivo aquí. Este archivo es necesario para que EhCache funcione correctamente. Define los nombres de caché y algunas propiedades de cada caché, como timeToLiveSeconds :
<ehcache xmlms:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <diskStore path="cache" /> <cache name="messageCache" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="0" timeToLiveSeconds="10" overflowToDisk="false" memoryStoreEvictionPolicy="LFU" /> </ehcache>Paso 4: Pruebe el caché
Ahora, con todo configurado y listo para funcionar, usar EhCache debería ser un trabajo fácil y feliz. Simplemente podemos agregar @Cacheable al método o clase que queremos almacenar en caché. Por ejemplo, agregué @Cacheable al método getMessage en MessageService . ¡Es fácil!
@Cacheable( cacheName = "messageCache" ) public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } Para probar que nuestro caché funciona, podemos crear un mensaje para ID=1 emitiendo una solicitud HTTP POST en http://localhost:8080/EhCacheExample/message/set/1/newMessage y luego obtener el mensaje para ID= 1 varias veces, con solicitudes GET a http://localhost:8080/EhCacheExample/message/1 . Como puede ver en la salida de la consola a continuación, el servicio web le pide al SOR que obtenga el mensaje la primera vez que lo solicitamos, pero no para las siguientes dos solicitudes, sino que devuelve el mensaje almacenado en caché. Debido a que definimos timeToLiveSeconds en 10, el servicio web llama al SOR para recibir el mensaje nuevamente después de 10 segundos:
set message [newMessage] at Sun Dec 06 23:55:39 MST 2015 get message [newMessage] at Sun Dec 06 23:55:42 MST 2015 Getting data from SOR...... get message [newMessage] at Sun Dec 06 23:55:47 MST 2015 get message [newMessage] at Sun Dec 06 23:55:49 MST 2015 get message [newMessage] at Sun Dec 06 23:55:54 MST 2015 Getting data from SOR......Actualizar el caché
Ahora, estamos disfrutando de la velocidad y la comodidad que nos brinda un caché, y EhCache es lo suficientemente bueno como para actualizarse solo cada 10 segundos. Pero, ¿qué sucede si deseamos actualizarlo inmediatamente después de que se actualice nuestro SOR? EhCache Spring Annotation ofrece @TriggersRemove para eliminar claves específicas del caché cuando se llama al método anotado. En nuestra API de servicio de mensajes, el mensaje almacenado en caché debe eliminarse del caché cuando se llama a setMessage . Por lo tanto, la próxima vez que ingrese una solicitud getMessage , el caché obtendrá un registro nuevo del SOR:
@Cacheable( cacheName = "messageCache", keyGenerator = @KeyGenerator ( // method name is not included in cache key to work with @TriggersRemove name = "HashCodeCacheKeyGenerator", properties = @Property( name="includeMethod", value="false" ))) public String getMessage( Integer id ) { System.out.println( "Getting data from SOR......" ); return messages.get( id ); } @TriggersRemove( cacheName = "messageCache", keyGenerator = @KeyGenerator ( name = "HashCodeCacheKeyGenerator", properties = @Property( name="includeMethod", value="false" ))) public void setMessage( @PartialCacheKey Integer id, String message ) { messages.put( id, message ); } El administrador de caché utiliza un generador de claves para generar la clave de caché. Puede encontrar una lista de generadores de claves de caché predefinidos aquí. De forma predeterminada, @KeyGenerator consume tanto el nombre del método como los parámetros pasados para generar la clave de caché. Pero dado que queremos que el método setMessage genere la misma clave que getMessage y elimine el valor en caché asociado con esa clave, debemos usar solo la ID del mensaje como clave y eliminar el nombre del método para la generación de claves. Por lo tanto, establecemos que la propiedad includeMethod del generador de claves sea false para ambos métodos. Además, dado que setMessage tiene dos argumentos, usamos la anotación @PartialCacheKey de @PartialCacheKey en el parámetro id para especificar que es el único que debe usar el generador de claves. Finalmente, recuerde que configuramos un caché dedicado, messageCache , para este tipo de recurso, por lo que usar solo la ID para la clave no presenta peligro de conflictos con otros tipos de recursos.
Ahora, si hacemos varias solicitudes HTTP para el mensaje con ID=1, de la siguiente manera:
HTTP POST: http://localhost:8080/EhCacheExample/message/set/1/newMessage1 HTTP GET:http://localhost:8080/EhCacheExample/message/1 HTTP POST: http://localhost:8080/EhCacheExample/message/set/1/newMessage2 HTTP GET:http://localhost:8080/EhCacheExample/message/1La consola mostrará:
set message [newMessage1] at Tue Dec 08 17:53:44 MST 2015 get message [newMessage1] at Tue Dec 08 17:53:47 MST 2015 Getting data from SOR...... set message [newMessage2] at Tue Dec 08 17:53:50 MST 2015 get message [newMessage2] at Tue Dec 08 17:53:53 MST 2015 Getting data from SOR......Conclusión
La estructura final del proyecto se ve así:
En este ejemplo, primero creamos una aplicación web Spring MVC RESTful simple. Sin modificar ni una sola línea del código de la aplicación existente, integramos EhCache a la perfección en la aplicación utilizando EhCache Spring Annotations. Hemos demostrado que EhCache Spring Annotations es fácil de instalar (al agregar su dependencia de Maven) y elegante de usar (al agregar anotaciones a los métodos).
Otras lecturas
La documentación de EhCache se puede encontrar aquí y la documentación de EhCache Spring Annotations está aquí.
Además, consulte el proyecto de muestra descrito en este artículo en GitHub.
