Loading ...

O Blog de Tecnologia da Geofusion

Utilizando Spring Framework para cache de objetos no Redis com Spring-Data Redis

Utilizando Spring Framework para cache de objetos no Redis com Spring-Data Redis

Uma das estratégias mais simples e conhecidas para melhorar a performance de serviços, sejam aplicações web corporativas ou aplicações mobile é o conceito de cache.

Em aplicações Java existem muitas alternativas para realizar o cache de objetos, dentre as mais famosas estão o Memcached, um cache de objetos Java, o Ehcache, o framework padrão de cache de segundo nível do Hibernate e cache de tickets do CAS (Central Authentication Service) e o Infinispan que é a alternativa de cache de objetos da Red Hat/JBoss.

O assunto cache também gerou a JSR 107: JCache – Java Temporary Caching API, uma JSR criada para tratar esse tópico a nível de especificação Java na qual o EhCache, Infinispan, Hazelcast e Oracle Coherence implementam essa especificação.

Nesse artigo vamos utilizar uma nova estratégia: utilizar o banco de dados NoSQL Redis como cache de objetos e integrá-lo com nossa aplicação Spring através do Spring-Data Redis Cache.

 

O que é o Redis?

Redis é um banco de dados NoSQL open source do tipo chave-valor conhecido por seu alto desempenho. Basicamente pode ser utilizado como um banco de dados, cache e também como um Message Broker. Inicialmente desenvolvido por Salvatore Sanfilippo, o Redis é um banco de dados desenvolvido em linguagem C e possui clients para diversas linguagens como C/C++, Go, Node.js, Scala e é claro Java. Além isso, o Redis tem suporte a alta disponibilidade, particionamento e cluster.

 

Cache de objetos

A primeira etapa para adicionar suporte a cache do Spring com Redis em seu projeto Java é adicionar as seguintes dependências no seu pom.xml: a primeira delas, o Spring-Data Redis, que nesse artigo estamos utilizando a versão 1.7.1.RELEASE e o Jedis, o client padrão do Redis para Java na versão 2.8.1. Veja:

 

<properties>
    <spring-data-redis.version>1.7.1.RELEASE</spring-data-redis.version>
    <jedis.version>2.8.1</jedis.version>
</properties>

<!-- Spring Data Redis -->
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>${spring-data-redis.version}</version>
</dependency>

<!-- Jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>${jedis.version}</version>
</dependency>

 

O cache em nossa aplicação funcionará da seguinte forma: vamos anotar o retorno dos métodos que serão cacheados com a anotação @Cacheable e por “debaixo dos panos” o Spring se encarregará da geração da chave e comunicação com o Redis. Após adicionar as dependências, vamos configurar os beans necessários para cache. Abaixo está o arquivo applicationCacheContext.xml que possui a declaração dos beans do Spring-Data Redis e do Spring Cache. Esse arquivo deve ser importado pelo applicationContext.xml da sua aplicação:

 

<?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:p="http://www.springframework.org/schema/p"
	xmlns:context="http://www.springframework.org/schema/context"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:jee="http://www.springframework.org/schema/jee"
	  xmlns:cache="http://www.springframework.org/schema/cache" 	
	xsi:schemaLocation="
    					http://www.springframework.org/schema/beans 
    					http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
    					http://www.springframework.org/schema/context
    					http://www.springframework.org/schema/context/spring-context-4.2.xsd
    					http://www.springframework.org/schema/tx 
    					http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
    					http://www.springframework.org/schema/aop 
    					http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
    					http://www.springframework.org/schema/jee 
    					http://www.springframework.org/schema/jee/spring-jee-4.2.xsd
    					http://www.springframework.org/schema/cache
        				http://www.springframework.org/schema/cache/spring-cache.xsd">
    					
	<cache:annotation-driven cache-resolver="cacheResolver" />

	<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
		<property name="hostName" value="${redis.host}" />
		<property name="port" value="${redis.port}" />
		<property name="database" value="${redis.database}" />
		<property name="usePool" value="true" />
	</bean>
			
	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
		<property name="connectionFactory" ref="jedisConnectionFactory" />
	</bean>
		
	<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
		<constructor-arg index="0" ref="redisTemplate" />
		<property name="defaultExpiration" value="${redis.expiration}" />
	</bean>
	
	<bean id="cacheResolver" class="org.springframework.cache.interceptor.SimpleCacheResolver">
		<constructor-arg index="0" ref="cacheManager" />
	</bean> 
	
</beans>

 

Basicamente, a tag <cache:annotation-driven /> configura o suporte de cache no Spring Framework. Nessa tag configuramos o cache-resolver padrão que irá utilizar o Cache Manager org.springframework.data.redis.cache.RedisCacheManager que por sua vez necessita de um RedisTemplate configurado para sua utilização. Note primeiramente que utilizamos um connection pool para as conexões com o Redis a partir da classe JedisConnectionFactory e que o tempo de expiração das chaves no Redis é global (time-to- live) e definido no Cache Manager a partir da propriedade defaultExpiration.

 

Agora para configurar o cache para um método específico basta anotá-lo com a anotação org.springframework.cache.annotation.Cacheable e configurar o nome do cache.

 

@Cacheable("findAll")
public List Platform findAll() { … }

 

O Spring-Data Redis possui uma implementação padrão para a geração dos nomes das chaves, mas podemos utilizar a propriedade key de @Cacheable para especificar o formato. Esse formato suporta Spring Expression Language, tornando muito simples a geração das  chaves com base nos parâmetros do método. Veja:

 

@Cacheable(cacheNames={"findAll"}, key="#root.targetClass.name + '.' + #root.method.name + '(' + #name + ')'")
public List Game findAll(final String name) { … }

 

A propriedade key acima gera as chaves no formato:

 

“nome totalmente qualificado da classe”.”nome do método”(“valor do name”)

 

Outra alternativa é criar sua própria implementação de KeyGenerator (org.springframework.cache.interceptor.KeyGenerator) e especificá-la na propriedade keyGenerator da anotação @Cacheable ou torná-la a implementação default a partir da propriedade key-generator da tag <cache:annotation-driven /> do XML de configuração do Spring. Um exemplo de Key Generator que dinamicamente cria as chaves no formato do exemplo acima independente da quantidade de argumentos é apresentado a seguir.

 

public class MyKeyGenerator implements KeyGenerator {

	@Override
	public Object generate(Object target, Method method, Object... params) {
		final StringBuilder sb = new StringBuilder();
		
		sb.append(target.getClass().getName())
			.append(".")
			.append(method.getName())
			.append("(");
		
		if (params != null && params.length > 0) {
			final String p = Arrays.asList(params).toString();
			sb.append(p.substring(1, p.length()-1));
		}
		
		sb.append(")");
		
		return sb.toString();
	}
}

 

Cache condicional

 

Outra propriedade que pode ajudar muito em relação a cache é a propriedade condition, que permite que você faça um cache condicional utilizando também Spring Expression Language para compor a expressão. Exemplo:

 

@Cacheable(cacheNames={“calculate”}, condition=”#km > 500”)
public Route calculate(int km) { ... }

 

E a especificação?

 

Desde a versão 4.1, o Spring Framework é totalmente compatível com a JSR 107: JCache – Java Temporary Caching API, ou seja, podemos simplesmente substituir @Cacheable por @CacheResult do pacote javax.cache.annotation.

 

@CacheResult(cacheNames={ "findAll" })
public List Game findAll() { ... }

 

Tanto a anotação @EnableCaching quanto a tag XML <cache:annotation-driven /> vão habilitar automaticamente as anotações JCache se a JCache API e o módulo spring-context-support estiverem no classpath. No pom.xml ficaria assim:

 

<!-- JSR-107 API -->
<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
    <version>1.0.0</version>
</dependency>

<!-- Spring Context Support -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>${spring.version}</version>
</dependency>

 

Dessa forma apresentamos o básico sobre cache com Spring-Data Redis, mas o assunto não acabou por aí: o tema cache envolve outros assuntos como evict, cache update, cache-region e time-to-live que ficam para um próximo artigo.