這裏用的spring對redis的封裝spring-data-redis,主要是對RedisCacheManager作一個二次封裝。html
主要依賴包:java
<dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> <version>2.7.3</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>1.6.0.RELEASE</version> </dependency>
如下爲擴展類com.caiya.cache.redis.ExtendedRedisCacheManager:redis
package com.caiya.cache.redis; import org.apache.commons.lang3.StringUtils; import org.apache.log4j.Logger; import org.springframework.cache.Cache; import org.springframework.data.redis.cache.RedisCache; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCachePrefix; import org.springframework.data.redis.core.RedisOperations; import javax.script.ScriptEngine; import javax.script.ScriptEngineManager; import javax.script.ScriptException; import java.util.Collection; import java.util.Collections; import java.util.regex.Pattern; /** * CacheManager backed by a Simple Spring Redis (SSR) {@link RedisCache}. Spring Cache and * CacheManager doesn't support configuring expiration time per method (there is no dedicated parameter in cache * annotation to pass expiration time). This extension of {@link RedisCacheManager} overcomes this limitation and allow to * pass expiration time as a part of cache name. To define custom expiration on method as a cache name use concatenation * of specific cache name, separator and expiration e.g. * <p/> * <pre> * public class UserDAO { * * // cache name: userCache, expiration: 300s * @Cacheable("userCache#300")// or @Cacheable("#60 * 5") * public User getUser(String name) { * * } * } * </pre> * * @author caiya * @since 16/3/18 */ public class ExtendedRedisCacheManager extends RedisCacheManager { private static final Logger logger = Logger.getLogger(ExtendedRedisCacheManager.class); private static final ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("JavaScript"); private static final Pattern pattern = Pattern.compile("[+\\-*/%]"); private String defaultCacheName; private char separator = '#'; public ExtendedRedisCacheManager(RedisOperations redisOperations) { this(redisOperations, Collections.<String>emptyList()); } public ExtendedRedisCacheManager(RedisOperations redisOperations, Collection<String> cacheNames) { super(redisOperations, cacheNames); } @Override public Cache getCache(String name) { // try to get cache by name RedisCache cache = (RedisCache) super.getCache(name); if (cache != null) { return cache; } // there's no cache which has given name // find separator in cache name int index = name.lastIndexOf(getSeparator()); if (index < 0) { return null; } // split name by the separator String cacheName = name.substring(0, index); if(StringUtils.isBlank(cacheName)){ cacheName = defaultCacheName; } cache = (RedisCache) super.getCache(cacheName); if (cache == null) { return null; } // get expiration from name Long expiration = getExpiration(name, index); if (expiration == null || expiration < 0) { logger.warn("Default expiration time will be used for cache '{}' because cannot parse '{}', cacheName : " + cacheName + ", name : " + name); return cache; } return new RedisCache(cacheName, (isUsePrefix() ? getCachePrefix().prefix(cacheName) : null), getRedisOperations(), expiration); } public char getSeparator() { return separator; } /** * Char that separates cache name and expiration time, default: #. * * @param separator */ public void setSeparator(char separator) { this.separator = separator; } private Long getExpiration(final String name, final int separatorIndex) { Long expiration = null; String expirationAsString = name.substring(separatorIndex + 1); try { // calculate expiration, support arithmetic expressions. if(pattern.matcher(expirationAsString).find()){ expiration = (long) Double.parseDouble(scriptEngine.eval(expirationAsString).toString()); }else{ expiration = Long.parseLong(expirationAsString); } } catch (NumberFormatException ex) { logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), ex); } catch (ScriptException e) { logger.error(String.format("Cannnot separate expiration time from cache: '%s'", name), e); } return expiration; } @Override public void setUsePrefix(boolean usePrefix) { super.setUsePrefix(usePrefix); } @Override public void setCachePrefix(RedisCachePrefix cachePrefix) { super.setCachePrefix(cachePrefix); } public void setDefaultCacheName(String defaultCacheName) { this.defaultCacheName = defaultCacheName; } }
spring-redis.xml配置文件:spring
<?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:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.0.xsd"> <!-- Jedis PoolConfig --> <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxTotal" value="${redis.pool.maxTotal}"/> <property name="maxIdle" value="${redis.pool.maxIdle}"/> <property name="minIdle" value="${redis.pool.minIdle}"/> <property name="maxWaitMillis" value="${redis.pool.maxWait}"/> <!--對拿到的connection進行validateObject校驗--> <property name="testOnBorrow" value="${redis.pool.testOnBorrow}"/> <!--在進行returnObject對返回的connection進行validateObject校驗--> <property name="testOnReturn" value="${redis.pool.testOnReturn}"/> <!--定時對線程池中空閒的連接進行validateObject校驗--> <property name="testWhileIdle" value="${redis.pool.testWhileIdle}"/> </bean> <!-- Redis SentinelConfig--> <bean id="redisSentinelConfiguration" class="org.springframework.data.redis.connection.RedisSentinelConfiguration"> <property name="master"> <bean class="org.springframework.data.redis.connection.RedisNode"> <property name="name" value="mymaster"/> </bean> </property> <property name="sentinels"> <set> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.sentinel1.host}"></constructor-arg> <constructor-arg name="port" value="${redis.sentinel1.port}"></constructor-arg> </bean> <bean class="org.springframework.data.redis.connection.RedisNode"> <constructor-arg name="host" value="${redis.sentinel2.host}"></constructor-arg> <constructor-arg name="port" value="${redis.sentinel2.port}"></constructor-arg> </bean> </set> </property> </bean> <!-- Jedis ConnectionFactory --> <bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"> <constructor-arg name="sentinelConfig" ref="redisSentinelConfiguration"></constructor-arg> <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg> </bean> <!-- Redis Template --> <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> </bean> <!-- 開啓緩存註解 --> <cache:annotation-driven/> <!-- 自定義CacheManager,實現緩存有效期可配置 --> <bean name="cacheManager" class="com.caiya.cache.redis.ExtendedRedisCacheManager"> <constructor-arg name="redisOperations" ref="redisTemplate"/> <constructor-arg name="cacheNames"> <set> <value>caiya_a</value> <value>caiya_test</value> </set> </constructor-arg> <!-- 默認緩存名字 --> <property name="defaultCacheName" value="caiya_a"/> <!-- 是否在容器啓動時初始化 --> <property name="loadRemoteCachesOnStartup" value="true"/> <!-- 是否使用前綴 --> <property name="usePrefix" value="true"/> <!-- 前綴命名,僅當usePrefix爲true時才生效 --> <property name="cachePrefix"> <bean class="org.springframework.data.redis.cache.DefaultRedisCachePrefix"> <constructor-arg name="delimiter" value=":"/> </bean> </property> <!-- 緩存名字和有效期的分隔符 --> <property name="separator" value="#"/> <!-- 默認有效期1h --> <property name="defaultExpiration" value="3600"/> <!-- 多個緩存有效期,通常的單個工程能夠省略此項 --> <property name="expires"> <map> <entry key="caiya_a" value="1800"/> </map> </property> </bean> </beans>
註解使用(注意key的生成策略):express
/** * @description 本方法在redis實例中的key爲:"caiya_a:querySoftwareList_null_null_null_null_1_10",默認會將cacheName追加在最前面. * 查詢軟件列表 * @param cid 類目ID * @param origin 來源 * @param sortField 排序字段 * @param sortType 排序順序 * @param pageNo 當前頁 * @param pageSize 每頁大小 * @return 軟件包裝器 */ @Cacheable(value = "caiya_a#(60 * 10)", key = "'querySoftwareList_' + #cid + '_' + #origin + '_' + #sortField + '_' + #sortType + '_' + #pageNo + '_' + #pageSize") public SoftwareWrapper querySoftwareList(Long cid, String origin, String sortField, String sortType, Integer pageNo, Integer pageSize) { ...... return ...; }
關於spring的spEL表達式可參考:apache
http://docs.spring.io/spring-framework/docs/4.1.x/spring-framework-reference/html/cache.html http://stackoverflow.com/questions/14072380/cacheable-key-on-multiple-method-arguments