一、配置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(); }