Cómo usar la cache de Spring en Alfresco

En este post vamos a explicar los pasos específicos que hay que seguir para utilizar la cache de Spring en Alfresco, ya que si no se conocen, es fácil que se convierta en una tarea ardua su configuración y uso.

Ni falta hace recordar que la cache de Spring es un mecanismo ideal para métodos computacionalmente pesados, que suelen llamarse varias veces con los mismos parámetros, para más información pinchar aquí.

La pregunta es, ¿y cómo utilizo la cache de Spring en Alfresco?

Por suerte, en Spring Surf se puede utilizar dicho mecanismo, para ello en primer lugar debemos añadir una serie de cambios en nuestro contexto, es decir, si estas trabajando con un amp, dirígete al archivo \src\main\amp\config\alfresco\module\alfresco-repo\module-context.xml y añade el siguiente código para añadir el bean cacheManager:

<cache:annotation-driven />
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" name="cacheName" />
</set>
</property>
</bean>

Si intentamos utilizar directamente la cache, sin cambiar nada más, obtendremos excepciones como las siguientes:
org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 4 in XML document from class path resource [alfresco/module/alfresco-repo/context/service-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 8; cvc-elt.1.a: Cannot find the declaration of element 'beans'.
o
nested exception is org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 8 in XML document from class path resource [alfresco/module/alfresco-repo/context/service-context.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 8; columnNumber: 112; Attribute "xmlns" must be declared for element type "beans".

Para solucionar esto, comenta

<!--  <!DOCTYPE beans PUBLIC '-//SPRING//DTD BEAN//EN' 'http://www.springframework.org/dtd/spring-beans.dtd'> -->

y sustituye la etiqueta <bean> por

&lt;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: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/cache http://www.springframework.org/schema/cache/spring-cache.xsd"&gt;

Con todo esto, ya tenemos la cache de Spring lista para ser usada.

Ahora queda utilizarla, para usar la cache en una clase debemos en primer lugar asegurarnos que la clase tiene una interfaz definida, y en segundo lugar añadir al método en concreto la etiqueta @Cacheable(«cacheName»), así un ejemplo de uso podría ser

public class SecurityService implements ISecurityService {
...
/**gets group's permissions list
* The result is cached using the key (groupId).
*
* @param groupId
* @return
*/
@Cacheable("userService")//Cache this method in userService cache
@Override
public Set&lt;String&gt; getGroupPermissions(String groupId) {
...
}

Nota: Las llamadas dentro de una misma clase no se cachean. Luego veremos un truco para solucionar este inconveniente.

Aunque parece que ya hemos terminado, aún falta algunos detalles que tenemos que tener en cuenta para evitarnos grandes quebraderos de cabeza. En primer lugar, para utilizar el bean cuyo método va a ser cacheado, debemos inyectar su interfaz, en lugar de la implementación, sino obtendremos la siguiente excepción:

java.lang.ClassCastException: com.sun.proxy.$Proxy158 cannot be cast to info.SecurityService

Esto se produce porque al cachear elementos, spring no trabaja directamente con la implementación, sino con un proxy que implementa la interfaz (esto explica el porque es obligatorio que exista una interfaz del bean), así que para solucionar este problema es necesario hacer un casting en la inyección del bean a la interfaz.

Es decir, inyecta el bean de la siguiente forma:

@Autowired
@Qualifier("id.bean.securityService ")
private ISecurityService securityService ;

Por último si queremos cachear métodos que son utilizados dentro de una misma clase, podemos aplicar una solución parecida a la siguiente, en donde se obtiene el mismo bean desde el contexto:

@Service
@Transactional(readOnly=true)
public class SettingServiceImpl implements SettingService {

@Inject
private SettingRepository settingRepository;

@Inject
private ApplicationContext applicationContext;

@Override
@Cacheable("settingsCache")
public String findValue(String name) {
Setting setting = settingRepository.findOne(name);
if(setting == null){
return null;
}
return setting.getValue();
}

@Override
public Boolean findBoolean(String name) {
String value = getSpringProxy().findValue(name);
if (value == null) {
return null;
}
return Boolean.valueOf(value);
}

/**
* Use proxy to hit cache
*/
private SettingService getSpringProxy() {
return applicationContext.getBean(SettingService.class);
}
...