Configurando Tempos de Expiração Personalizados para o Spring Cache com Redis

Introdução

A gestão de cache é um componente crucial para otimizar o desempenho de aplicações, reduzindo a latência e a carga sobre os bancos de dados ou serviços externos. O Spring Framework oferece uma abstração de cache robusta via Spring Cache, que pode ser facilmente integrada com diversas tecnologias de cache. Este artigo explora como configurar e utilizar o Spring Cache com Redis para aplicar tempos de vida (TTLs) específicos para diferentes caches, aproveitando a flexibilidade e o armazenamento distribuído do Redis.

Adicionando Dependências

Para integrar o Spring Cache com Redis, é necessário incluir as dependências apropriadas no arquivo pom.xml do seu projeto Maven (ou equivalente para Gradle):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

A dependência spring-boot-starter-data-redis fornece a integração com o Redis, enquanto commons-pool2 é necessária para o pool de conexões do Lettuce (cliente Redis padrão do Spring Boot).

Configuração do Redis no Application Properties

As propriedades de conexão com o servidor Redis são definidas no arquivo application.properties ou application.yml:

# Endereço do servidor Redis
spring.redis.host=127.0.0.1
# Porta do servidor Redis
spring.redis.port=6379
# Senha do servidor Redis (se houver, caso contrário, deixe vazio)
spring.redis.password=
# Tipo de cache, geralmente configurado automaticamente
spring.cache.type=redis
# Tempo limite de conexão com o Redis em milissegundos
spring.redis.timeout=10000
# Base de dados Redis a ser utilizada (0-15)
spring.redis.database=0
# Configurações do pool de conexões Lettuce
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

Configuração Personalizada do CacheManager

Para definir tempos de expiração específicos para diferentes caches, é preciso configurar o CacheManager. Isso geralmente é feito em uma classe de configuração Spring.

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;

import java.lang.reflect.Method;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

@Configuration
public class ConfiguracaoCacheRedis {

    /**
     * Configura o CacheManager para usar Redis, permitindo TTLs personalizados.
     * @param connectionFactory A fábrica de conexões Redis.
     * @return Uma instância de CacheManager configurada.
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // Configuração padrão de serialização e tempo de vida para caches não especificados.
        RedisCacheConfiguration defaultConfig = criarConfiguracaoCache(30 * 60); // 30 minutos padrão

        Map<String, RedisCacheConfiguration> cacheSpecificConfigs = new HashMap<>();
        // Configurações personalizadas por nome de cache.
        cacheSpecificConfigs.put("cacheDiario", criarConfiguracaoCache(24 * 60 * 60)); // 1 dia
        cacheSpecificConfigs.put("cacheCurtoPrazo", criarConfiguracaoCache(5));     // 5 segundos

        return RedisCacheManager.builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
                                .cacheDefaults(defaultConfig)
                                .withInitialCacheConfigurations(cacheSpecificConfigs)
                                .build();
    }

    /**
     * Cria uma configuração de cache Redis com serializador JSON e TTL.
     * @param ttlSeconds Tempo de vida em segundos para o cache.
     * @return Uma configuração de cache Redis.
     */
    private RedisCacheConfiguration criarConfiguracaoCache(long ttlSeconds) {
        // Serializador para converter objetos Java em JSON para o Redis.
        Jackson2JsonRedisSerializer<Object> jsonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // Habilita tipagem padrão para lidar com a desserialização de tipos complexos ou polimórficos.
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL);
        jsonSerializer.setObjectMapper(mapper);

        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jsonSerializer))
                .entryTtl(Duration.ofSeconds(ttlSeconds)); // Define o tempo de vida.
    }

    /**
     * Define um gerador de chaves personalizado para o Spring Cache.
     * Isso permite criar chaves de cache mais descritivas e customizadas.
     * A chave gerada terá o formato: NomeDaClasse::NomeDoMetodo?arg1-arg2-...
     * @return Uma instância de KeyGenerator.
     */
    @Bean
    public KeyGenerator geradorDeChavesPersonalizado() {
        return (alvo, metodo, argumentos) -> {
            StringBuilder chave = new StringBuilder();
            chave.append(alvo.getClass().getSimpleName()).append("::");
            chave.append(metodo.getName());

            if (argumentos == null || argumentos.length == 0) {
                return chave.toString();
            }

            // Concatena apenas os argumentos não nulos
            String argsString = Arrays.stream(argumentos)
                                      .filter(Objects::nonNull)
                                      .map(Object::toString)
                                      .collect(Collectors.joining("-"));

            if (!argsString.isEmpty()) {
                chave.append("?").append(argsString);
            }
            return chave.toString();
        };
    }
}

Nesta configuração:

  • O método cacheManager é responsável por construir o RedisCacheManager. Ele define uma política padrão de 30 minutos e um mapa de configurações específicas para os nomes de cache "cacheDiario" (1 dia) e "cacheCurtoPrazo" (5 segundos).
  • O método criarConfiguracaoCache é um utilitário que cria uma RedisCacheConfiguration com base em um TTL fornecido. Ele utiliza Jackson2JsonRedisSerializer para serializar os objetos em JSON antes de armazená-los no Redis, garantindo que objetos complexos sejam armazenados e recuperados corretamente.
  • O geradorDeChavesPersonalizado cria chaves de cache mais legíveis e consistentes, combinando o nome da classe, método e argumentos.

Utilizando Caches com TTLs Diferenciados

Com o CacheManager configurado, você pode usar a anotação @Cacheable em seus serviços, especificando o nome do cache que corresponde a uma das configurações de TTL definidas:

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalTime;

@Service
public class ServicoDados {

    /**
     * Busca um dado que expira rapidamente (5 segundos).
     * Usa o cache "cacheCurtoPrazo".
     * @param id Identificador do dado.
     * @return O dado simulado.
     */
    @Cacheable(value = "cacheCurtoPrazo")
    public String buscarDadoCurtoPrazo(String id) {
        System.out.println("Buscando dado de curto prazo para id: " + id + " no banco de dados...");
        // Simula uma operação de banco de dados ou serviço externo
        try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        return "Dado de Curto Prazo para " + id + " @ " + LocalTime.now();
    }

    /**
     * Busca um dado que tem um tempo de vida mais longo (1 dia).
     * Usa o cache "cacheDiario".
     * @param code Código do dado.
     * @return O dado simulado.
     */
    @Cacheable(value = "cacheDiario")
    public String buscarDadoDiario(Integer code) {
        System.out.println("Buscando dado diário para código: " + code + " no banco de dados...");
        // Simula uma operação de banco de dados ou serviço externo
        try { Thread.sleep(1000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); }
        return "Dado Diário para código " + code + " @ " + LocalDate.now();
    }
}

Neste exemplo, o método buscarDadoCurtoPrazo utilizará a configuração de cache com 5 segundos de TTL, enquanto buscarDadoDiario usará a configuração de 1 dia. O Spring Cache gerenciará automaticamente o armazenamento e a expiração no Redis conforme as políticas definidas.

Tags: Spring Boot Spring Cache Redis cache TTL

Publicado em 7-11 10:45