pringCache是SpringFramework3.1引入的新特性,提供了基于注解的缓存配置方法。
作用
当我们在调用一个缓存方法时会根据相关信息和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回
配置
1、pom.xml文件中添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2、redis
安装和配置redis
redis.key.prefix=prefix
redis.host=host
redis.port=6379
redis.timeout=10000
redis.password=123456
redis.database=1
3、spring配置中配置redis
<cache:annotation-driven cache-manager="redisManager"/>
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}" />
<property name="maxTotal" value="${redis.maxActive}" />
<property name="maxWaitMillis" value="${redis.maxWait}" />
<property name="testOnBorrow" value="${redis.testOnBorrow}" />
</bean>
<bean id="connectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="poolConfig" ref="poolConfig" />
<property name="port" value="${redis.port}" />
<property name="hostName" value="${redis.host}" />
<property name="password" value="${redis.pass}" />
<property name="timeout" value="${redis.timeout}" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="connectionFactory" />
</bean>
4、配置spring-cache
<bean id="redisManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<!--用户信息缓存-->
<bean class="com.core.utils.RedisCache">
<property name="redisTemplate" ref="redisTemplate"/>
<property name="name" value="UserCache"/>
<property name="timeout" value="${redis.expireTime.seconds}"/>
</bean>
</set>
</property>
</bean>
5、RedisCache
public class RedisCache implements Cache {
/**
* 日志对象
*/
protected Logger logger = LoggerFactory.getLogger(getClass());
/**
* redis模版
*/
private RedisTemplate<String, Object> redisTemplate;
/**
* 缓存名称
*/
private String name;
/**
* 超时时间(秒)
*/
private long timeout;
/**
* 偏移量(秒)
*/
private int offset = 0;
/**
* 清除系统缓存
*/
@Override
public void clear() {
redisTemplate.execute(new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
connection.flushDb();
return "ok";
}
});
}
/**
* 根据key删除缓存
*/
@Override
public void evict(Object key) {
logger.debug("删除" + key);
if (null != key) {
byte[][] keys = null;
if (key.getClass().isArray()) {
int len = Array.getLength(key);
if (len <= 0) {
logger.info("删除len={}", len);
return;
}
keys = new byte[len][];
for (int i = 0; i < len; ++i) {
keys[i] = Array.get(key, i).toString().getBytes();
}
} else {
keys = new byte[1][];
keys[0] = key.toString().getBytes();
}
if (logger.isDebugEnabled()) {
String[] strKeys = new String[keys.length];
for (int i = 0; i < strKeys.length; ++i) {
strKeys[i] = new String(keys[i]);
}
logger.debug("删除keys={}", JsonMapper.toJsonString(strKeys));
}
final byte[][] redisKeys = keys;
redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
return connection.del(redisKeys);
}
});
}
}
/**
* 根据key获取缓存
*/
@Override
public ValueWrapper get(Object key) {
logger.debug("获取:" + key);
final String keyf = key.toString();
Object object = null;
object = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
byte[] key = keyf.getBytes();
byte[] value = connection.get(key);
if (value == null) {
return null;
}
return SerializationUtils.deserialize(value);
}
});
ValueWrapper obj = (object != null ? new SimpleValueWrapper(object) : null);
return obj;
}
/**
* 加入缓存
*/
@Override
public void put(Object key, Object value) {
logger.debug("put key:" + key + " value:" + value);
final String keyString = key.toString();
final Object valuef = value;
final long liveTime = getTimeout();
redisTemplate.execute(new RedisCallback<Long>() {
@Override
public Long doInRedis(RedisConnection connection) throws DataAccessException {
byte[] keyb = keyString.getBytes();
byte[] valueb = SerializationUtils.serialize((Serializable) valuef);
connection.set(keyb, valueb);
if (liveTime > 0) {
connection.expire(keyb, liveTime);
}
return 1L;
}
});
}
@Override
public <T> T get(Object arg0, Class<T> arg1) {
return null;
}
@Override
public String getName() {
return this.name;
}
@Override
public Object getNativeCache() {
return this.redisTemplate;
}
@Override
public ValueWrapper putIfAbsent(Object arg0, Object arg1) {
return null;
}
public RedisTemplate<String, Object> getRedisTemplate() {
return redisTemplate;
}
public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
this.redisTemplate = redisTemplate;
}
public void setName(String name) {
this.name = name;
}
public long getTimeout() {
if (getOffset() > 0) {
// 设置偏移量,防止缓存雪崩
int addOffset = new Random().nextInt(getOffset());
return (timeout + addOffset);
}
return timeout;
}
public void setTimeout(long timeout) {
this.timeout = timeout;
}
public int getOffset() {
return offset;
}
public void setOffset(int offset) {
this.offset = offset;
}
6、多参数key生成器 CacheKeyGenerator
配置项:
<bean id="defaultKeyGenerator" class="com.stnts.core.utils.CacheKeyGenerator"/>
代码
public class CacheKeyGenerator implements KeyGenerator {
/**
* 日志对象
*/
public Logger logger = LoggerFactory.getLogger(getClass());
// custom cache key
public static final int NO_PARAM_KEY = 0;
public static final int NULL_PARAM_KEY = 53;
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder key = new StringBuilder();
key.append(target.getClass().getSimpleName()).append(".")
.append(method.getName()).append(":");
if (params.length == 0) {
return key.append(NO_PARAM_KEY).toString();
}
for (Object param : params) {
if (param == null) {
logger.warn("input null param for Spring cache, use default key={}",
NULL_PARAM_KEY);
key.append(NULL_PARAM_KEY);
} else if (ClassUtils.isPrimitiveArray(param.getClass())) {
int length = Array.getLength(param);
for (int i = 0; i < length; i++) {
key.append(Array.get(param, i));
key.append(',');
}
} else if (ClassUtils.isPrimitiveOrWrapper(param.getClass())
|| param instanceof String) {
key.append(param);
} else {
logger.warn("Using an object as a cache key may lead to unexpected results. " +"Either use @Cacheable(key=..) or implement CacheKey. Method is " + target.getClass() + "#" + method.getName());
key.append(param.hashCode());
}
key.append('-');
}
String finalKey = key.toString();
long cacheKeyHash = Hashing.murmur3_128().hashString(finalKey, Charset.defaultCharset()).asLong();
logger.debug("using cache key={} hashCode={}", finalKey, cacheKeyHash);
return key.toString();
}
7、@Cacheable、@CachePut、@CacheEvict 注释介绍
@Cacheable 作用和配置方法
| @Cacheable 的作用 | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 | |
|---|---|---|
| @Cacheable 主要的参数 | ||
| value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
| key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
| condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
@CachePut 作用和配置方法
| @CachePut 的作用 | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 | |
|---|---|---|
| @CachePut 主要的参数 | ||
| value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
| key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
| condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
@CacheEvict 作用和配置方法
| @CachEvict 的作用 | 主要针对方法配置,能够根据一定的条件对缓存进行清空 | |
|---|---|---|
| @CacheEvict 主要的参数 | ||
| value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @CachEvict(value=”mycache”) 或者 @CachEvict(value={”cache1”,”cache2”} |
| key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @CachEvict(value=”testcache”,key=”#userName”) |
| condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才清空缓存 | 例如: @CachEvict(value=”testcache”, condition=”#userName.length()>2”) |
| allEntries | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | 例如: @CachEvict(value=”testcache”,allEntries=true) |
| beforeInvocation | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
spring cache 是基于动态生成的 proxy 代理机制来对方法的调用进行切面,这里关键点是对象的引用问题,如果对象的方法是内部调用(即 this 引用)而不是外部引用,则会导致 proxy 失效,那么我们的切面就失效,也就是说上面定义的各种注释包括 @Cacheable、@CachePut 和 @CacheEvict 都会失效。
@CacheEvict 的可靠性问题
我们看到,@CacheEvict 注释有一个属性 beforeInvocation,缺省为 false,即缺省情况下,都是在实际的方法执行完成后,才对缓存进行清空操作。期间如果执行方法出现异常,则会导致缓存清空不被执行。用 @CacheEvict 注释提供的 beforeInvocation 属性,将其设置为 true,这样,在方法执行前我们的缓存就被清空了

本文深入解析Spring Cache特性,介绍其配置步骤,包括pom.xml依赖添加、Redis安装配置、spring配置等,以及如何通过@Cacheable、@CachePut、@CacheEvict注解实现缓存管理。探讨缓存机制、多参数key生成器CacheKeyGenerator和注解配置细节。
638

被折叠的 条评论
为什么被折叠?



