Spring cache abstraction.
0. Índice de contenidos.
- 1. Introducción.
- 2. Entorno.
- 3. Dependencias.
- 4. Configuración.
- 5. Testándolo.
- 6. Referencias.
- 7. Conclusiones.
1. Introducción
A partir de la versión 3.1 M1 de Spring disponemos de un soporte nativo para cache de modo que podemos cachear las invocaciones a métodos de servicios de Spring.
No se sí se habrán inspirado en este tutorial de Carlos,
Spring AOP: Cacheando aplicaciones
usando anotaciones y aspectos con Aspectj ;), pero el concepto es el mismo, eso sí, ahora lo tenemos en el core de spring.
Como con el soporte de transaccionalidad, el soporte de cache de spring es muy simple de configurar y afecta mínimamente a nuestro código porque hacemos uso
del mismo a través de anotaciones.
Si trabajamos con el soporte de persistencia de hibernate, por ejemplo, podemos hacer uso de la cache de segundo nivel para el acceso a datos;
si bien, con el soporte de cache de spring podemos elevar el nivel de cache a la capa de servicios de manera que no solo cachearemos datos sino que podremos cachear
también el resultado de lógica de negocio o el acceso a datos de fuentes externos.
En este tutorial vamos a ver cómo configurar nuestra aplicación para que haga uso de la cache de spring con el soporte por defecto de ehcache, haremos un pequeño ejemplo de cacheo/descacheo y
comprobaremos cómo acceder a la cache de forma programática.
2. Entorno.
El tutorial está escrito usando el siguiente entorno:
- Hardware: Portátil MacBook Pro 17′ (2.93 GHz Intel Core 2 Duo, 4GB DDR3 SDRAM).
- Sistema Operativo: Mac OS X Snow Leopard 10.6.1
- Spring 3.1 M1
- EhCache 2.4.2
3. Dependencias.
Para hacer uso del soporte de cache de spring necesitaremos incluir en nuestro proyecto las siguientes dependencias, como siempre, lo mostramos haciendo uso de maven:
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.1.0.M1</version> </dependency> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache-core</artifactId> <version>2.4.2</version> </dependency>
Para nuestras pruebas en el entorno de tests con jUnit y el soporte de inyección de spring, serán necesarias las siguientes:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>3.1.0.M1</version> <scope>test</scope> </dependency> </dependencies>
4. Configuración.
La configuración a nivel de Spring es la siguiente:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-3.1.xsd "> <!-- el soporte de anotaciones genérico --> <context:annotation-config /> <context:component-scan base-package="com.autentia.tutoriales" /> <!-- el soporte de anotaciones para el uso de la cache --> <cache:annotation-driven /> <!-- el Manager de la cache haciendo uso del soporte por defecto de ehcache --> <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager"> <property name="cacheManager" ref="ehcache"/> </bean> <!-- la configuración de las caches haciendo uso del fichero de propiedades típico de ehcache --> <bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean" p:config-location="classpath:ehcache.xml"/> </beans>
Y la configuración propia de las regiones de ehcache podría ser algo como esto, dependiendo de las regiones de cache que necesitemos y la estrategia de almacenamiento de las mismas:
<?xml version="1.0" encoding="UTF-8" ?> <ehcache updateCheck="false"> <cache name="alarms" maxElementsInMemory="1000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" maxElementsOnDisk="1000000" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> </ehcache>
El nombre del fichero lo hemos definido en la configuración de spring como ehcache.xml y debe ubicarse en el classpath de la aplicación (en src/main/resources/ u otra ubicación ‘visible’).
Para más información acerca de los parámetros de configuración de las regiones de ehcache podemos revisar su propia documentación.
5. Testándolo.
Una vez que disponemos de las dependencias y la configuración vamos a empezar por testar el servicio que esperamos que nos devuelva el resultado de sus métodos cacheados, así como que disponga de un método de descacheo.
Para una prueba simple, asumiremos que necesitamos un servicio que nos devuelva alarmas en el sistema cacheadas y que al crear una nueva alarma se debería descachear la lista de alarmas. Para simplificar, las alarmas no serán
un objeto complejo, sino simplemente una cadena
package com.autentia.tutoriales.spring; import javax.annotation.Resource; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:applicationContext-test.xml" }) public class AlarmRepositoryTest { private static final String ALARM_CACHE_REGION_NAME = "alarms"; @Resource private AlarmRepository alarmRepository; @Resource private CacheManager cacheManager; @Before public void initAlarmCache(){ alarmRepository.getAll(); } @Test public void theAlarmsMustBeInCache(){ assertTheNumberOfObjectsInCache(ALARM_CACHE_REGION_NAME,1); } @Test public void theAlarmsMustEvictFromCacheBeInCache(){ alarmRepository.createNew("Be careful..."); assertTheNumberOfObjectsInCache(ALARM_CACHE_REGION_NAME,0); } private void assertTheNumberOfObjectsInCache(String regionName, int numberOfObjectsExpected) { final Cache<String, Object> alarmCache = cacheManager.getCache(regionName); Assert.assertEquals(numberOfObjectsExpected, ((net.sf.ehcache.Cache) alarmCache.getNativeCache()).getSize()); } }
Con este simple test estamos cubriendo varias facetas:
- estamos haciendo uso del soporte de spring para inyección de dependencias en el entorno de test,
- estamos inyectando un servicio que nos dará acceso a las alarmas y a crear una nueva AlarmRepository, está clase aún no existe, estamos aún en la fase ROJA,
- en un método que se ejecuta antes de la invocación a cada método de test con @Before, incovamos a un método del servicio anterior que obtiene las alarmas y las cachea, este método tampoco existe aún,
- estamos inyectando el manager de la cache que nos permitirá acceder a la instancia de la región de cache que vamos a definir para comprobar el número de objetos en la misma,
- en el primer test comprobamos que existe un objeto en la cache,
- en el segundo test comprobamos que no existe ningún objeto en la cache después de invocar al método de crear una nueva alarma, puesto que este queremos que descachee,
Vamos a pasar del ROJO al VERDE creando la interfaz del servicio de alarmas y, con ello, veremos como hacer uso de las anotaciones de cache de spring.
package com.autentia.tutoriales.spring; import java.util.List; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; public interface AlarmRepository { @Cacheable(value="alarms") List<String> getAll(); @CacheEvict(value="alarms",allEntries=true) void createNew(String text); }
- con @Cacheable indicamos que las llamadas a un método se cachearán en la región definida por el atributo value, que debe coincidir con una de las regiones definidas en la configuración de ehcache.
- con @CacheEvict indicamos que la llamada a ese método liberará la cache de la región definida por el atributo value y con allEntries=true liberamos toda la cache indiscriminadamente.
Con la configuración de anotaciones no solo podemos hacer esto, también podemos indicar que los objetos se añadirán a la cache con una clave que puede ser la conjunción de los parámetros del método, solo uno de ellos o incluso una propiedad de uno de los parámetros haciendo uso de Spring Expression Language. Del mismo modo el descacheo lo podemos hacer selectivo para que solo se elimine de la cache ciertos registros y no todos.
Con la configuración anterior ya tenemos lista nuestra cache del servicio y ahora solo nos queda implementar el servicio real, esto es, la clase de implementación.
package com.autentia.tutoriales.spring; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import org.springframework.stereotype.Service; @Service(value="alarmRepository") public class MockAlarmRepository implements AlarmRepository { private List<String> alarms = Arrays.asList(new String[]{"Night alarm","alarm clock"}); public List<String> getAll(){ System.out.println("reading all the alarms..."); return alarms; } public void createNew(String text) { alarms = new ArrayList<String>(alarms); alarms.add(text); } }
6. Referencias.
- http://blog.springsource.com/2011/02/23/spring-3-1-m1-caching/
- http://ehcache.org/documentation/configuration.html
7. Conclusiones.
Cuando proponemos Spring como engranaje de las distintas capas de la arquitectura de nuestras aplicaciones no solo nos beneficiamos del contexto de inyección, sino de todas aquellas
facilidades que Spring pone a nuestra disposición para, en definitiva, hacernos el desarrollo más sencillo.
En este caso podemos habilitar el soporte de cache de spring con ehcache de una manera sencilla y hacer uso de la misma en nuestros servicios de una forma nada intrusiva.
Un saludo.
Jose
Buen tutorial, buen concepto
Hola…. genial el tutorial muy actualizado..
pero sale el siguiente error:
Caused by: net.sf.ehcache.CacheException: Error configuring from input stream. Initial cause was null:11: Element does not allow attribute \\\»maxElementsOnDisk\\\».
at net.sf.ehcache.config.ConfigurationFactory.parseConfiguration(ConfigurationFactory.java:147)
at net.sf.ehcache.CacheManager.parseConfiguration(CacheManager.java:235)
at net.sf.ehcache.CacheManager.init(CacheManager.java:190)
at net.sf.ehcache.CacheManager.(CacheManager.java:172)
at org.springframework.cache.ehcache.EhCacheManagerFactoryBean.afterPropertiesSet(EhCacheManagerFactoryBean.java:104)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1479)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1417)
… 63 more
Si puedes ayudarme
Gracias,
Buen tutorial pero hace falta ver un ejemplo de como limpiar el cache de forma selectiva, si no esto se queda a medias
Puedes hacer uso del @CacheEvict colocando en el atributo key el valor que deseas liberar (sin enviar el valor que se corresponde con esa key claro está).