1. 程式人生 > >spring-data-redis時效設置

spring-data-redis時效設置

back fig rgs lis lena substr sync exceptio enum

本人轉自http://hbxflihua.iteye.com/blog/2320584#bc2396403

spring目前在@Cacheable和@CacheEvict等註解上不支持緩存時效設置,只允許通過配置文件設置全局時效。這樣就很不方便設定時間。比如系統參數和業務數據的時效是不一樣的,這給程序開發造成很大的困擾。不得已,我重寫了spring的這兩個註解。以下是具體實現。

首先定義@Cacheable和@CacheEvict註解類。

Java代碼 技術分享圖片
  1. package com.lh.common.annotation;
  2. import java.lang.annotation.ElementType;
  3. import java.lang.annotation.Retention;
  4. import java.lang.annotation.RetentionPolicy;
  5. import java.lang.annotation.Target;
  6. import com.rd.ifaes.common.dict.ExpireTime;
  7. @Retention(RetentionPolicy.RUNTIME)
  8. @Target({ElementType.METHOD})
  9. public @interface Cacheable {
  10. public String key() default ""; // 緩存key
  11. public ExpireTime expire() default ExpireTime.NONE; // 緩存時效,默認無限期
  12. }

Java代碼 技術分享圖片
  1. package com.lh.common.annotation;
  2. import java.lang.annotation.Documented;
  3. import java.lang.annotation.ElementType;
  4. import java.lang.annotation.Inherited;
  5. import java.lang.annotation.Retention;
  6. import java.lang.annotation.RetentionPolicy;
  7. import java.lang.annotation.Target;
  8. /**
  9. * 緩存清除
  10. * @author lh
  11. * @version 3.0
  12. * @since 2016-8-28
  13. *
  14. */
  15. @Target({ ElementType.METHOD })
  16. @Retention(RetentionPolicy.RUNTIME)
  17. @Inherited
  18. @Documented
  19. public @interface CacheEvict {
  20. String key() default "";// 緩存key
  21. }

具體的切面代碼(CacheAspect.java)如下:

Java代碼 技術分享圖片
  1. package com.lh.common.annotation;
  2. import java.util.List;
  3. import java.util.Set;
  4. import java.util.concurrent.TimeUnit;
  5. import org.aspectj.lang.ProceedingJoinPoint;
  6. import org.aspectj.lang.annotation.Around;
  7. import org.aspectj.lang.annotation.Aspect;
  8. import org.springframework.beans.factory.annotation.Autowired;
  9. import org.springframework.data.redis.core.RedisTemplate;
  10. import org.springframework.data.redis.core.ValueOperations;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.util.CollectionUtils;
  13. import com.rd.ifaes.common.util.ReflectionUtils;
  14. import com.rd.ifaes.common.util.StringUtils;
  15. import com.rd.ifaes.core.core.util.CacheUtils;
  16. @Aspect
  17. @Component
  18. public class CacheAspect {
  19. @SuppressWarnings("rawtypes")
  20. @Autowired
  21. private RedisTemplate redisTemplate;
  22. @Around("@annotation(cache)")
  23. public Object cacheable(final ProceedingJoinPoint pjp, Cacheable cache) throws Throwable {
  24. String key = getCacheKey(pjp, cache.key());
  25. // //方案一:使用自定義緩存工具類操作緩存
  26. // Object value = CacheUtils.getObj(key);// 從緩存獲取數據
  27. // if (value != null) {
  28. // return value; // 如果有數據,則直接返回
  29. // }
  30. // value = pjp.proceed(); // 緩存,到後端查詢數據
  31. // if (value != null) {
  32. // CacheUtils.set(key, value, cache.expire());
  33. // }
  34. // 方案二:使用redisTemplate操作緩存
  35. @SuppressWarnings("unchecked")
  36. ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
  37. Object value = valueOper.get(key); // 從緩存獲取數據
  38. if (value != null) {
  39. return value; // 如果有數據,則直接返回
  40. }
  41. value = pjp.proceed(); // 緩存,到後端查詢數據
  42. CacheUtils.set(key, value, cache.expire());
  43. if (cache.expire().getTime() <= 0) { // 如果沒有設置過期時間,則無限期緩存
  44. valueOper.set(key, value);
  45. } else { // 否則設置緩存時間
  46. valueOper.set(key, value, cache.expire().getTime(), TimeUnit.SECONDS);
  47. }
  48. return value;
  49. }
  50. @SuppressWarnings("unchecked")
  51. @Around("@annotation(evict)")
  52. public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict evict) throws Throwable {
  53. Object value = pjp.proceed(); // 執行方法
  54. String key = getCacheKey(pjp, evict.key());
  55. // //方案一:使用自定義緩存工具類操作緩存
  56. // CacheUtils.del(key);
  57. // 方案二:使用redisTemplate操作緩存
  58. if (evict.key().equals(key)) {// 支持批量刪除
  59. Set<String> keys = redisTemplate.keys(key.concat("*"));
  60. redisTemplate.delete(keys);
  61. }else{
  62. redisTemplate.delete(key);
  63. }
  64. return value;
  65. }
  66. /**
  67. * 獲取緩存的key值
  68. *
  69. * @param pjp
  70. * @param key
  71. * @return
  72. */
  73. private String getCacheKey(ProceedingJoinPoint pjp, String key) {
  74. StringBuilder buf = new StringBuilder();
  75. Object[] args = pjp.getArgs();
  76. if(StringUtils.isNotBlank(key)){
  77. buf.append(key);
  78. List<String> annoParamNames = AopUtils.getAnnoParams(key);
  79. String[] methodParamNames = AopUtils.getMethodParamNames(AopUtils.getMethod(pjp));
  80. if(!CollectionUtils.isEmpty(annoParamNames)){
  81. for (String ap : annoParamNames) {
  82. String paramValue = "";
  83. for (int i = 0; i < methodParamNames.length; i++) {
  84. if(ap.startsWith(methodParamNames[i])){
  85. Object arg = args[i];
  86. if (ap.contains(".")) {
  87. paramValue = String.valueOf(ReflectionUtils.invokeGetter(arg, ap.substring(ap.indexOf(".") + 1)));
  88. } else {
  89. paramValue = String.valueOf(arg);
  90. }
  91. }
  92. }
  93. int start = buf.indexOf("{" + ap);
  94. int end = start + ap.length() + 2;
  95. buf = buf.replace(start, end, paramValue);
  96. }
  97. }
  98. }else{
  99. buf.append(pjp.getSignature().getDeclaringTypeName()).append(":").append(pjp.getSignature().getName());
  100. for (Object arg : args) {
  101. buf.append(":").append(arg.toString());
  102. }
  103. }
  104. return buf.toString();
  105. }
  106. }

裏面使用到AopUtils.java和ExpireTime.java兩個類,具體代碼如下:

Java代碼 技術分享圖片
  1. package com.lh.common.annotation;
  2. import org.aspectj.lang.ProceedingJoinPoint;
  3. import org.aspectj.lang.Signature;
  4. import org.aspectj.lang.reflect.MethodSignature;
  5. import org.springframework.asm.*;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.lang.reflect.Method;
  9. import java.lang.reflect.Modifier;
  10. import java.util.ArrayList;
  11. import java.util.List;
  12. import java.util.regex.Matcher;
  13. import java.util.regex.Pattern;
  14. /**
  15. * 切面編程工具類
  16. * @author lh
  17. * @version 3.0
  18. * @since 2016-8-26
  19. */
  20. public class AopUtils {
  21. /**
  22. * <p>獲取方法的參數名</p>
  23. *
  24. * @param m
  25. * @return
  26. */
  27. public static String[] getMethodParamNames(final Method m) {
  28. final String[] paramNames = new String[m.getParameterTypes().length];
  29. final String n = m.getDeclaringClass().getName();
  30. final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
  31. String className = m.getDeclaringClass().getSimpleName();
  32. ClassReader cr = null;
  33. InputStream resourceAsStream = null;
  34. try {
  35. // cr = new ClassReader(n);
  36. // String filePathName = Class.forName(n).getResource("EDayHqbProcessManagerImpl.class").getPath();
  37. resourceAsStream = Class.forName(n).getResourceAsStream(className + ".class");
  38. cr = new ClassReader(resourceAsStream);
  39. // cr = new ClassReader(ClassLoader.getSystemResourceAsStream(n + ".class"));
  40. } catch (IOException e) {
  41. //e.printStackTrace();
  42. // Exceptions.uncheck(e);
  43. } catch (ClassNotFoundException e) {
  44. //e.printStackTrace();
  45. } finally {
  46. if (resourceAsStream != null) {
  47. try {
  48. resourceAsStream.close();
  49. } catch (IOException e) {
  50. e.printStackTrace();
  51. }
  52. }
  53. }
  54. assert cr != null;
  55. cr.accept(new ClassVisitor(Opcodes.ASM4, cw) {
  56. @Override
  57. public MethodVisitor visitMethod(final int access,
  58. final String name, final String desc,
  59. final String signature, final String[] exceptions) {
  60. final Type[] args = Type.getArgumentTypes(desc);
  61. // 方法名相同並且參數個數相同
  62. if (!name.equals(m.getName())
  63. || !sameType(args, m.getParameterTypes())) {
  64. return super.visitMethod(access, name, desc, signature,
  65. exceptions);
  66. }
  67. MethodVisitor v = cv.visitMethod(access, name, desc, signature,
  68. exceptions);
  69. return new MethodVisitor(Opcodes.ASM4, v) {
  70. @Override
  71. public void visitLocalVariable(String name, String desc,
  72. String signature, Label start, Label end, int index) {
  73. int i = index - 1;
  74. // 如果是靜態方法,則第一就是參數
  75. // 如果不是靜態方法,則第一個是"this",然後才是方法的參數
  76. if (Modifier.isStatic(m.getModifiers())) {
  77. i = index;
  78. }
  79. if (i >= 0 && i < paramNames.length) {
  80. paramNames[i] = name;
  81. }
  82. super.visitLocalVariable(name, desc, signature, start,
  83. end, index);
  84. }
  85. };
  86. }
  87. }, 0);
  88. return paramNames;
  89. }
  90. /**
  91. * <p>比較參數類型是否一致</p>
  92. *
  93. * @param types asm的類型({@link Type})
  94. * @param clazzes java 類型({@link Class})
  95. * @return
  96. */
  97. private static boolean sameType(Type[] types, Class<?>[] clazzes) {
  98. // 個數不同
  99. if (types.length != clazzes.length) {
  100. return false;
  101. }
  102. for (int i = 0; i < types.length; i++) {
  103. if (!Type.getType(clazzes[i]).equals(types[i])) {
  104. return false;
  105. }
  106. }
  107. return true;
  108. }
  109. /**
  110. * 取得切面調用的方法
  111. * @param pjp
  112. * @return
  113. */
  114. public static Method getMethod(ProceedingJoinPoint pjp){
  115. Signature sig = pjp.getSignature();
  116. MethodSignature msig = null;
  117. if (!(sig instanceof MethodSignature)) {
  118. throw new IllegalArgumentException("該註解只能用於方法");
  119. }
  120. msig = (MethodSignature) sig;
  121. Object target = pjp.getTarget();
  122. Method currentMethod = null;
  123. try {
  124. currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
  125. } catch (NoSuchMethodException e) {
  126. } catch (SecurityException e) {
  127. }
  128. return currentMethod;
  129. }
  130. public static List<String> getMatcher(String regex, String source) {
  131. List<String> list = new ArrayList<String>();
  132. Pattern pattern = Pattern.compile(regex);
  133. Matcher matcher = pattern.matcher(source);
  134. while (matcher.find()) {
  135. list.add(matcher.group());
  136. }
  137. return list;
  138. }
  139. /**
  140. * 取得註解參數
  141. (?=exp) 匹配exp前面的位置
  142. (?<=exp) 匹配exp後面的位置
  143. (?!exp) 匹配後面跟的不是exp的位置
  144. (?<!exp) 匹配前面不是exp的位置
  145. * @param managers
  146. * @return
  147. */
  148. public static List<String> getAnnoParams(String source){
  149. String regex = "(?<=\\{)(.+?)(?=\\})";
  150. return getMatcher(regex, source);
  151. }
  152. }

Java代碼 技術分享圖片
  1. package com.lh.common.dict;
  2. /**
  3. * 失效時間枚舉類
  4. * @author lh
  5. * @version 3.0
  6. * @since 2016-8-25
  7. *
  8. */
  9. public enum ExpireTime {
  10. /**
  11. * 無固定期限
  12. */
  13. NONE(0, "無固定期限")
  14. /**
  15. * 1秒鐘
  16. */
  17. ,ONE_SEC(1, "1秒鐘")
  18. /**
  19. * 5秒鐘
  20. */
  21. ,FIVE_SEC(5, "5秒鐘")
  22. /**
  23. * 10秒鐘
  24. */
  25. ,TEN_SEC(10, "10秒鐘")
  26. /**
  27. * 30秒鐘
  28. */
  29. ,HALF_A_MIN(30, "30秒鐘")
  30. /**
  31. * 1分鐘
  32. */
  33. ,ONE_MIN(60, "1分鐘")
  34. /**
  35. * 5分鐘
  36. */
  37. ,FIVE_MIN(5 * 60, "5分鐘")
  38. /**
  39. * 10分鐘
  40. */
  41. ,TEN_MIN(10 * 60, "10分鐘")
  42. /**
  43. * 20分鐘
  44. */
  45. ,TWENTY_MIN(20 * 60, "20分鐘")
  46. /**
  47. * 30分鐘
  48. */
  49. ,HALF_AN_HOUR(30 * 60, "30分鐘")
  50. /**
  51. * 1小時
  52. */
  53. ,ONE_HOUR(60 * 60, "1小時")
  54. /**
  55. * 1天
  56. */
  57. ,ONE_DAY(24 * 60 * 60, "1天")
  58. /**
  59. * 1個月
  60. */
  61. ,ONE_MON(30 * 24 * 60 * 60, "1個月")
  62. /**
  63. * 1年
  64. */
  65. ,ONE_YEAR(365 * 24 * 60 * 60, "1年")
  66. ;
  67. /**
  68. * 時間
  69. */
  70. private final int time;
  71. /**
  72. * 描述
  73. */
  74. private final String desc;
  75. ExpireTime(int time, String desc) {
  76. this.time = time;
  77. this.desc = desc;
  78. }
  79. /**
  80. * 獲取具體時間
  81. * @return
  82. */
  83. public int getTime() {
  84. return time;
  85. }
  86. /**
  87. * 獲取時間描述信息
  88. * @return
  89. */
  90. public String getDesc() {
  91. return desc;
  92. }
  93. /**
  94. * 根據時間匹配失效期限
  95. * @param time
  96. * @return
  97. */
  98. public static ExpireTime match(int time){
  99. if(NONE.getTime() == time){
  100. return NONE;
  101. }else if(ONE_SEC.getTime() == time){
  102. return ONE_SEC;
  103. }else if(FIVE_SEC.getTime() == time){
  104. return FIVE_SEC;
  105. }else if(TEN_SEC.getTime() == time){
  106. return TEN_SEC;
  107. }else if(HALF_A_MIN.getTime() == time){
  108. return HALF_A_MIN;
  109. }else if(ONE_MIN.getTime() == time){
  110. return ONE_MIN;
  111. }else if(FIVE_MIN.getTime() == time){
  112. return FIVE_MIN;
  113. }else if(TEN_MIN.getTime() == time){
  114. return TEN_MIN;
  115. }else if(TWENTY_MIN.getTime() == time){
  116. return TWENTY_MIN;
  117. }else if(HALF_AN_HOUR.getTime() == time){
  118. return HALF_AN_HOUR;
  119. }else if(ONE_HOUR.getTime() == time){
  120. return ONE_HOUR;
  121. }else if(ONE_DAY.getTime() == time){
  122. return ONE_DAY;
  123. }else if(ONE_MON.getTime() == time){
  124. return ONE_MON;
  125. }else if(ONE_YEAR.getTime() == time){
  126. return ONE_YEAR;
  127. }
  128. return HALF_AN_HOUR;
  129. }
  130. }

配置中的RdRedisCache.java 代碼如下:

Java代碼 技術分享圖片
  1. package com.lh.common.jedis;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.IOException;
  5. import java.io.ObjectInputStream;
  6. import java.io.ObjectOutputStream;
  7. import net.sf.ehcache.Element;
  8. import org.springframework.cache.Cache;
  9. import org.springframework.cache.support.SimpleValueWrapper;
  10. import org.springframework.dao.DataAccessException;
  11. import org.springframework.data.redis.connection.RedisConnection;
  12. import org.springframework.data.redis.core.RedisCallback;
  13. import org.springframework.data.redis.core.RedisTemplate;
  14. public class RdRedisCache implements Cache {
  15. private RedisTemplate<String, Object> redisTemplate;
  16. private String name;
  17. public RedisTemplate<String, Object> getRedisTemplate() {
  18. return redisTemplate;
  19. }
  20. public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
  21. this.redisTemplate = redisTemplate;
  22. }
  23. public void setName(String name) {
  24. this.name = name;
  25. }
  26. @Override
  27. public String getName() {
  28. return this.name;
  29. }
  30. @Override
  31. public Object getNativeCache() {
  32. return this.redisTemplate;
  33. }
  34. @Override
  35. public ValueWrapper get(Object key) {
  36. final String keyf = obj2Str(key);
  37. Object object = null;
  38. object = redisTemplate.execute(new RedisCallback<Object>() {
  39. public Object doInRedis(RedisConnection connection)
  40. throws DataAccessException {
  41. byte[] key = keyf.getBytes();
  42. byte[] value = connection.get(key);
  43. if (value == null) {
  44. return null;
  45. }
  46. return toObject(value);
  47. }
  48. });
  49. return (object != null ? new SimpleValueWrapper(object) : null);
  50. }
  51. @Override
  52. public void put(Object key, Object value) {
  53. final String keyf = obj2Str(key);
  54. final Object valuef = value;
  55. final long liveTime = 86400;
  56. redisTemplate.execute(new RedisCallback<Long>() {
  57. public Long doInRedis(RedisConnection connection)
  58. throws DataAccessException {
  59. byte[] keyb = keyf.getBytes();
  60. byte[] valueb = toByteArray(valuef);
  61. connection.set(keyb, valueb);
  62. if (liveTime > 0) {
  63. connection.expire(keyb, liveTime);
  64. }
  65. return 1L;
  66. }
  67. });
  68. }
  69. public String obj2Str(Object key){
  70. String keyStr = null;
  71. if(key instanceof Integer){
  72. keyStr = ((Integer)key).toString();
  73. }else if(key instanceof Long){
  74. keyStr = ((Long)key).toString();
  75. }else {
  76. keyStr = (String)key;
  77. }
  78. return keyStr;
  79. }
  80. /**
  81. * 描述 : <Object轉byte[]>. <br>
  82. * <p>
  83. * <使用方法說明>
  84. * </p>
  85. *
  86. * @param obj
  87. * @return
  88. */
  89. private byte[] toByteArray(Object obj) {
  90. byte[] bytes = null;
  91. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  92. try {
  93. ObjectOutputStream oos = new ObjectOutputStream(bos);
  94. oos.writeObject(obj);
  95. oos.flush();
  96. bytes = bos.toByteArray();
  97. oos.close();
  98. bos.close();
  99. } catch (IOException ex) {
  100. ex.printStackTrace();
  101. }
  102. return bytes;
  103. }
  104. /**
  105. * 描述 : <byte[]轉Object>. <br>
  106. * <p>
  107. * <使用方法說明>
  108. * </p>
  109. *
  110. * @param bytes
  111. * @return
  112. */
  113. private Object toObject(byte[] bytes) {
  114. Object obj = null;
  115. try {
  116. ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
  117. ObjectInputStream ois = new ObjectInputStream(bis);
  118. obj = ois.readObject();
  119. ois.close();
  120. bis.close();
  121. } catch (IOException ex) {
  122. ex.printStackTrace();
  123. } catch (ClassNotFoundException ex) {
  124. ex.printStackTrace();
  125. }
  126. return obj;
  127. }
  128. @Override
  129. public void evict(Object key) {
  130. final String keyf = obj2Str(key);
  131. redisTemplate.execute(new RedisCallback<Long>() {
  132. public Long doInRedis(RedisConnection connection)
  133. throws DataAccessException {
  134. return connection.del(keyf.getBytes());
  135. }
  136. });
  137. }
  138. @Override
  139. public void clear() {
  140. redisTemplate.execute(new RedisCallback<String>() {
  141. public String doInRedis(RedisConnection connection)
  142. throws DataAccessException {
  143. connection.flushDb();
  144. return "ok";
  145. }
  146. });
  147. }
  148. @Override
  149. public <T> T get(Object key, Class<T> type) {
  150. ValueWrapper wrapper = get(key);
  151. return wrapper == null ? null : (T) wrapper.get();
  152. }
  153. @Override
  154. public ValueWrapper putIfAbsent(Object key, Object value) {
  155. synchronized (key) {
  156. ValueWrapper wrapper = get(key);
  157. if (wrapper != null) {
  158. return wrapper;
  159. }
  160. put(key, value);
  161. return toWrapper(new Element(key, value));
  162. }
  163. }
  164. private ValueWrapper toWrapper(Element element) {
  165. return (element != null ? new SimpleValueWrapper(element.getObjectValue()) : null);
  166. }
  167. }

spring配置文件的相關配置如下:

Xml代碼 技術分享圖片
  1. <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
  2. <property name="maxIdle" value="${redis.pool.maxIdle}" /> <!-- 最大能夠保持idel狀態的對象數 -->
  3. <property name="maxTotal" value="${redis.pool.maxTotal}" /> <!-- 最大分配的對象數 -->
  4. <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <!-- 當調用borrow Object方法時,是否進行有效性檢查 -->
  5. </bean>
  6. <!-- jedisPool init -->
  7. <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
  8. <constructor-arg index="0" ref="jedisPoolConfig" />
  9. <constructor-arg index="1" value="${redis.host}" type="String" />
  10. <constructor-arg index="2" value="${redis.port}" type="int" />
  11. </bean>
  12. <!-- jedis單機配置 -->
  13. <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
  14. <property name="hostName" value="${redis.host}" />
  15. <property name="port" value="${redis.port1}" />
  16. <property name="timeout" value="${redis.timeout}" />
  17. <property name="poolConfig" ref="jedisPoolConfig" />
  18. </bean>
  19. <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
  20. <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
  21. p:connectionFactory-ref="jedisConnFactory" p:keySerializer-ref="stringRedisSerializer" />
  22. <!-- spring自己的緩存管理器 -->
  23. <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
  24. <property name="caches">
  25. <set>
  26. <bean class="com.lh.common.jedis.RdRedisCache" p:redis-template-ref="redisTemplate" p:name="sysCache"/>
  27. </set>
  28. </property>
  29. </bean>
  30. <!-- 啟用緩存註解功能,這個是必須的,否則註解不會生效,另外,該註解一定要聲明在spring主配置文件中才會生效 -->
  31. <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true" />

spring-data-redis時效設置