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) {
  // 業務代碼省略
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM