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