SpringCache整合Redis实现自定义缓存时间

Spring Cache简介

Spring3.1开始引入了的基于注释(annotation)的缓存(cache)技术,它本质上不是一个具体的缓存实现方案,而是一个对缓存使用的抽象,通过在既有代码中添加注解,即能够达到缓存方法的返回对象的效果。

Spring 的缓存技术还具备相当的灵活性,不仅能够使用 SpEL 来定义缓存的 key 和各种 condition,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如 Redis 集成。

@Cacheable

这个用的比较多,用在查询方法上,先从缓存中读取,如果缓存不存在再调用该方法获取数据,然后把返回的数据添加到缓存中去。

@Cacheable(value = "userCache", key = "targetClass + '.' + methodName + '.' + "#userid")
public User getEntity(long userid) {
  // 业务代码省略
}

@CacheEvict

清空缓存

@CacheEvict(value = "userCache", key = "targetClass + '.' + methodName + '.' + "#userid")
public boolean delete(long userid) {
  // 业务代码省略
}

@CachePut

这个注释可以确保方法被执行,同时方法的返回值也被记录到缓存中,实现缓存与数据库的同步更新。

@CachePut(value = "userCache", key = "targetClass + '.' + methodName + '.' + "#user.getUserid")
public User save(User user) {
  // 业务代码省略
}

注:每个注解都有多个参数,这里不一一列出,建议进入源码查看注释。

缺点

虽然Spring Cache用起来很方便的, 但不支持设置动态过期时间,这里需要重写RedisCacheManager的一些方法。

示例

这里用的spring对redis的封装spring-data-redis,主要是对RedisCacheManager做一个二次封装。

导包

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.8.4.RELEASE</version>
</dependency>

重写 RedisCacheManager

package com.demo.cache;

import java.util.Objects;
import java.util.regex.Pattern;

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.data.redis.cache.RedisCache;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.core.RedisOperations;

import lombok.extern.log4j.Log4j2;

/**
 * 重写redis缓存管理器
 * <p>
 * 重写 RedisCacheManager createCache 方法
 * <p>
 * 在缓存名字上添加过期时间表达式 如:cachename#60*60
 * @author czk
 */
@Log4j2
public class ExtendedRedisCacheManager extends RedisCacheManager {

	private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript");

	private static final Pattern pattern = Pattern.compile("[+\-*/%]");
	
	/**
	 * 分隔符
	 */
	private char separator = '#';

	public ExtendedRedisCacheManager(@SuppressWarnings("rawtypes") RedisOperations redisOperations) {
	    super(redisOperations);
	}

	@Override
	@SuppressWarnings("unchecked")
	protected RedisCache createCache(String cacheName) {
	    // 获取默认时间
	    long expiration = computeExpiration(cacheName);		
	    int index = cacheName.indexOf(this.getSeparator());
	    if (index > 0) {
	        expiration = getExpiration(cacheName, index, expiration);
	    }
	    return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null),
		        getRedisOperations(), expiration);
	}

	/**
	 * 计算缓存时间
	 * @param name 缓存名字 cache#60*60
	 * @param separatorIndex 分隔符位置
	 * @param defalutExp 默认缓存时间
	 * @return
	 */
	protected long getExpiration(final String name, final int separatorIndex, final long defalutExp) {
	    Long expiration = null;
	    String expirationAsString = name.substring(separatorIndex + 1);
	    try {
	        if (pattern.matcher(expirationAsString).find()) {
	            expiration = NumberUtils.toLong(scriptEngine.eval(expirationAsString).toString(), defalutExp);
	        } else {
	            expiration = NumberUtils.toLong(expirationAsString, defalutExp);
	        }
	    } catch (ScriptException e) {
	        log.error("缓存时间转换错误:{},异常:{}", name, e.getMessage());
	    }
	    return Objects.nonNull(expiration) ? expiration.longValue() : defalutExp;
	}

	public char getSeparator() {
	    return separator;
	}

	public void setSeparator(char separator) {
	    this.separator = separator;
	}
}

spring-redis.xml配置文件

<?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:cache="http://www.springframework.org/schema/cache"
	xsi:schemaLocation="http://www.springframework.org/schema/beans      
                        http://www.springframework.org/schema/beans/spring-beans-4.3.xsd      
                        http://www.springframework.org/schema/context      
                        http://www.springframework.org/schema/context/spring-context-4.3.xsd      
                        http://www.springframework.org/schema/cache   
                        http://www.springframework.org/schema/cache/spring-cache-4.3.xsd">
	
	<context:property-placeholder location="classpath:redis.properties" />    
	
	<!-- 启用缓存注解功能,否则注解不会生效 -->
	<cache:annotation-driven cache-manager="cacheManager" />

	<!-- redis 相关配置 -->
	<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
	    <property name="maxIdle" value="${redis.maxIdle}" />
	    <property name="maxWaitMillis" value="${redis.maxWait}" />
	    <property name="testOnBorrow" value="${redis.testOnBorrow}" />
	</bean>

	<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
	    p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.password}"
	    p:database="${redis.database}" p:timeout="${redis.timeout}"
	    p:pool-config-ref="poolConfig" />

	<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
	    <property name="connectionFactory" ref="jedisConnectionFactory" />
	    <!--对key的序列化器 -->
	    <property name="keySerializer">
	        <bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
	    </property>
	    <!--是对value的列化器 默认:JdkSerializationRedisSerializer -->
	    <property name="valueSerializer">
	        <bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
	    </property>	
	</bean>

	<!-- 扩展RedisCacheManager -->
	<bean id="cacheManager" class="com.demo.cache.ExtendedRedisCacheManager">
	    <constructor-arg ref="redisTemplate" />
	    <!-- 是否使用前缀 默认: -->
	    <property name="usePrefix" value="true" />
	    <!-- 默认有效期1h (60 * 60 = 3600秒) -->
	    <property name="defaultExpiration" value="3600" />
	</bean>

</beans>

redis.properties

#redis 缓存配置
redis.host=127.0.0.1
redis.port=6379
redis.password=
redis.database=0
# 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例  
redis.maxIdle=300
redis.maxctive=6000
# 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间(毫秒),则直接抛出JedisConnectionException; 
redis.maxWait=10000
#在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的    
redis.testOnBorrow=true
#读超时时间
redis.timeout=30000

注: Spring Cache是采用AOP来管理缓存,所有通过this调用的方法多不会触发缓存,key采用的是StringRedisSerializer序列化,所有key必须为String类型。

@Cacheable指定缓存5分钟

@Cacheable(value = "userCache#60*5", key = "targetClass + '.' + methodName + '.' + "#userid")
public User getEntity(long userid) {
  // 业务代码省略
}
所有的进步都是不稳定, 一个问题解决了又不得不面对一个新的问题。
原文地址:https://www.cnblogs.com/nyvi/p/8613065.html