一、配置pom.xmljava
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.7.2.RELEASE</version> </dependency>
二、配置springweb
cache:annotation-driven开启注解,其中的选项mode这里要说明一下,proxy|aspectj指定使用哪一种代理方式。proxy代理使用的是基于spring-AOP技术,aspectj代理则使用的Aspectj切面技术。若是使用aspectj选项须要引入对spring-aspects.jar的依赖,并开启load-time weaving (or compile-time weaving)功能。redis
其中碰到的坑是注解不生效。缘由是,在默认状况下,mode为proxy代理,代码在同一个具体类的方法中去调用该类的另外一个方法时,被调用的方法被设置了cache注解,可是因为spring AOP不支持类中方法间的调用的代理。解决办法是将被调用的方法重构到另外一个类中,cache注解生效。spring
<!-- 启用缓存注解功能,这个是必须的,不然注解不会生效,另外,该注解必定要声明在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" />
三、数据查询方法实现缓存
查询时,支持根据指定参数flush判断是否刷新缓存。其中flush参数不进行缓存的key生成app
@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; }
四、缓存key注解ide
在生成key时,指定include为false的参数不进行拼接函数
@Target({ ElementType.PARAMETER }) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface KeyParam { boolean include() default true; }
五、缓存key生成器fetch
解析请求参数,若是注解KeyParam的参数指定include为false则不进行拼接。ui
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(); } }
六、改造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); } }
七、自定义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(); }