1、配置pom.xml
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.2.RELEASE</version>
</dependency>
2、配置spring
cache:annotation-driven开启注解,其中的选项mode这里要说明一下,proxy|aspectj指定使用哪种代理方式。proxy代理使用的是基于spring-AOP技术,aspectj代理则使用的Aspectj切面技术。如果使用aspectj选项需要引入对spring-aspects.jar的依赖,并开启load-time weaving (or compile-time weaving)功能。
其中碰到的坑是注解不生效。原因是,在默认情况下,mode为proxy代理,代码在同一个具体类的方法中去调用该类的另一个方法时,被调用的方法被设置了cache注解,但是由于spring AOP不支持类中方法间的调用的代理。解决办法是将被调用的方法重构到另一个类中,cache注解生效。
<!-- 启用缓存注解功能,这个是必须的,否则注解不会生效,另外,该注解一定要声明在spring主配置文件中才会生效 -->
<cache:annotation-driven cache-manager="cacheManager" key-generator="keyGenerator" />
<bean id="jedisConnectionFactory"
class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}" />
<property name="port" value="${redis.port}" />
<property name="timeout" value="${redis.timeout}" />
<!-- 引用配置文件 spring-context-jedis.xml 中的bean -->
<property name="poolConfig" ref="jedisPoolConfig" />
</bean>
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnectionFactory" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="com.frbao.web.common.cache.RedisObjectSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="com.frbao.web.common.cache.RedisObjectSerializer" />
</property>
</bean>
<!-- spring自己的缓存管理器,这里定义了缓存位置名称 ,即注解中的value -->
<bean id="cacheManager" class="com.frbao.web.common.cache.RedisCacheManager">
<constructor-arg index="0" ref="redisTemplate" />
</bean>
<!-- 方法上使用@Cacheable时,针对方法处理的key的生成器 -->
<bean id="keyGenerator" class="com.frbao.web.common.cache.DefaultCacheMethodKeyGenerator" />
3、数据查询方法实现
查询时,支持根据指定参数flush判断是否刷新缓存。其中flush参数不进行缓存的key生成
@CachePut(value = MethodCacheKey.PROJECLIST, condition = "#flush")
@Cacheable(value = MethodCacheKey.PROJECLIST, condition = "!#flush")
public ObjectListDto fetchProjectList(final FetchProjectListParameter parameterObject,
@KeyParam(include = false) boolean flush) {
ObjectListDto objectList = new ObjectListDto();
objectList = RemoteInvoker.doInvoke(new InvocationHandler<ObjectListDto>() {
@Override
public ObjectListDto execute() {
if (parameterObject.orderBy == 0) {
return projectListService.selectProjectList(parameterObject.itype, parameterObject.fyearincomerate,
parameterObject.idaycount, parameterObject.pageNo, parameterObject.pageSize);
} else {
return projectListService.selectProjectListOrderbyPage(parameterObject.itype,
parameterObject.fyearincomerate, parameterObject.idaycount, parameterObject.orderBy,
parameterObject.pageNo, parameterObject.pageSize);
}
}
}, InterfaceNo.INVEST_0_002);
return objectList;
}
4、缓存key注解
在生成key时,指定include为false的参数不进行拼接
@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface KeyParam {
boolean include() default true;
}
5、缓存key生成器
解析请求参数,如果注解KeyParam的参数指定include为false则不进行拼接。
public class DefaultCacheMethodKeyGenerator implements KeyGenerator {
/**
* 对参数进行拼接后MD5
*/
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(".").append(method.getName());
Annotation[][] paraAnnosArry = method.getParameterAnnotations();
int i = 0;// 参数索引
StringBuilder paramsSb = new StringBuilder();
for (Annotation[] paraAnnos : paraAnnosArry) {
KeyParam key = null;
for (Annotation anno : paraAnnos) {
if (KeyParam.class.isInstance(anno)) {// 如果存在指定是否生成键值注解,进行解析
key = (KeyParam) anno;
break;
}
}
// 如果不指定,默认生成包含到键值中
if ((key == null || key.include()) && params[i] != null) {
paramsSb.append(String.valueOf(params[i]));
}
i++;
}
if (paramsSb.length() > 0) {
sb.append("_").append(MD5.encode(paramsSb.toString()));
}
return sb.toString();
}
}
6、改造CacheManager,支持不同的cache实现(替换原RedisCache实现)
通过简单的继承,重写createAndAddCache方法,返回自定义的RedisCache实现。
import org.springframework.cache.Cache;
import org.springframework.data.redis.core.RedisOperations;
public class RedisCacheManager extends org.springframework.data.redis.cache.RedisCacheManager {
/**
* 构造函数
*
* @param redisOperations
*/
public RedisCacheManager(@SuppressWarnings("rawtypes") RedisOperations redisOperations) {
super(redisOperations);
}
@Override
protected Cache createAndAddCache(String cacheName) {
addCache(createDiffCache(cacheName));
return super.getCache(cacheName);
}
@SuppressWarnings("unchecked")
private RedisCache createDiffCache(String cacheName) {
long expiration = computeExpiration(cacheName);
return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(),
expiration);
}
}
7、自定义RedisCache,支持按照不同的待缓存的数据(key/value)配置缓存时间
由于原RedisCache实现的设置缓存,其中缓存时间是从cacheMetadata成员变量中获得,但是其属性却为私有和final定义,因此不能通过简单的继承进行重写。
private final RedisCacheMetadata cacheMetadata;
@Override
public void put(final Object key, final Object value) {
put(new RedisCacheElement(new RedisCacheKey(key).usePrefix(cacheMetadata.getKeyPrefix()).withKeySerializer(
redisOperations.getKeySerializer()), value).expireAfter(cacheMetadata.getDefaultExpiration()));
}
因此通过代理方式,在基于原Cache接口上主要是重写put和putIfAbsent方法
实现上如下代码在RedisCacheElement上调用expireAfter方法。
expireAfter(ExpireCacheStratergies.getExpireSeconds(redisCache.getName(), value))
public class RedisCache implements Cache {
private final byte[] keyPrefix;
private final org.springframework.data.redis.cache.RedisCache redisCache;
/**
* Constructs a new <code>RedisCache</code> instance.
*
* @param name cache name
* @param prefix
* @param redisOperations
* @param expiration
*/
public RedisCache(String name, byte[] prefix, RedisOperations<? extends Object, ? extends Object> redisOperations,
long expiration) {
hasText(name, "non-empty cache name is required");
keyPrefix = prefix;
redisCache = new org.springframework.data.redis.cache.RedisCache(name, prefix, redisOperations, expiration);
}
/**
* Return the value to which this cache maps the specified key, generically specifying a type that return value will be cast
* to.
*
* @param key
* @param type
* @return
* @see DATAREDIS-243
*/
public <T> T get(Object key, Class<T> type) {
return redisCache.get(key, type);
}
/*
* (non-Javadoc)
* @see org.springframework.cache.Cache#get(java.lang.Object)
*/
@Override
public ValueWrapper get(Object key) {
return redisCache.get(key);
}
/*
* (non-Javadoc)
* @see org.springframework.cache.Cache#put(java.lang.Object, java.lang.Object)
*/
@Override
public void put(final Object key, final Object value) {
@SuppressWarnings("rawtypes")
RedisOperations redisOperations = (RedisOperations) redisCache.getNativeCache();
redisCache.put(new RedisCacheElement(
new RedisCacheKey(key).usePrefix(keyPrefix).withKeySerializer(redisOperations.getKeySerializer()), value)
.expireAfter(ExpireCacheStratergies.getExpireSeconds(redisCache.getName(), value)));
}
/*
* (non-Javadoc)
* @see org.springframework.cache.Cache#putIfAbsent(java.lang.Object, java.lang.Object)
*/
public ValueWrapper putIfAbsent(Object key, final Object value) {
@SuppressWarnings("rawtypes")
RedisOperations redisOperations = (RedisOperations) redisCache.getNativeCache();
return redisCache.putIfAbsent(new RedisCacheElement(
new RedisCacheKey(key).usePrefix(keyPrefix).withKeySerializer(redisOperations.getKeySerializer()), value)
.expireAfter(ExpireCacheStratergies.getExpireSeconds(redisCache.getName(), value)));
}
/*
* (non-Javadoc)
* @see org.springframework.cache.Cache#evict(java.lang.Object)
*/
public void evict(Object key) {
redisCache.evict(key);
}
/*
* (non-Javadoc)
* @see org.springframework.cache.Cache#clear()
*/
public void clear() {
redisCache.clear();
}
/*
* (non-Javadoc)
* @see org.springframework.cache.Cache#getName()
*/
public String getName() {
return redisCache.getName();
}
/**
* {@inheritDoc} This implementation simply returns the RedisTemplate used for configuring the cache, giving access to the
* underlying Redis store.
*/
public Object getNativeCache() {
return redisCache.getNativeCache();
}