spring-data-redis時效設置
阿新 • • 發佈:2018-07-12
back fig rgs lis lena substr sync exceptio enum
本人轉自http://hbxflihua.iteye.com/blog/2320584#bc2396403
spring目前在@Cacheable和@CacheEvict等註解上不支持緩存時效設置,只允許通過配置文件設置全局時效。這樣就很不方便設定時間。比如系統參數和業務數據的時效是不一樣的,這給程序開發造成很大的困擾。不得已,我重寫了spring的這兩個註解。以下是具體實現。
首先定義@Cacheable和@CacheEvict註解類。
Java代碼- package com.lh.common.annotation;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- import com.rd.ifaes.common.dict.ExpireTime;
- @Retention(RetentionPolicy.RUNTIME)
- @Target({ElementType.METHOD})
- public @interface Cacheable {
- public String key() default ""; // 緩存key
- public ExpireTime expire() default ExpireTime.NONE; // 緩存時效,默認無限期
- }
Java代碼
- package com.lh.common.annotation;
- import java.lang.annotation.Documented;
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Inherited;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * 緩存清除
- * @author lh
- * @version 3.0
- * @since 2016-8-28
- *
- */
- @Target({ ElementType.METHOD })
- @Retention(RetentionPolicy.RUNTIME)
- @Inherited
- @Documented
- public @interface CacheEvict {
- String key() default "";// 緩存key
- }
具體的切面代碼(CacheAspect.java)如下:
Java代碼- package com.lh.common.annotation;
- import java.util.List;
- import java.util.Set;
- import java.util.concurrent.TimeUnit;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.annotation.Around;
- import org.aspectj.lang.annotation.Aspect;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.data.redis.core.RedisTemplate;
- import org.springframework.data.redis.core.ValueOperations;
- import org.springframework.stereotype.Component;
- import org.springframework.util.CollectionUtils;
- import com.rd.ifaes.common.util.ReflectionUtils;
- import com.rd.ifaes.common.util.StringUtils;
- import com.rd.ifaes.core.core.util.CacheUtils;
- @Aspect
- @Component
- public class CacheAspect {
- @SuppressWarnings("rawtypes")
- @Autowired
- private RedisTemplate redisTemplate;
- @Around("@annotation(cache)")
- public Object cacheable(final ProceedingJoinPoint pjp, Cacheable cache) throws Throwable {
- String key = getCacheKey(pjp, cache.key());
- // //方案一:使用自定義緩存工具類操作緩存
- // Object value = CacheUtils.getObj(key);// 從緩存獲取數據
- // if (value != null) {
- // return value; // 如果有數據,則直接返回
- // }
- // value = pjp.proceed(); // 緩存,到後端查詢數據
- // if (value != null) {
- // CacheUtils.set(key, value, cache.expire());
- // }
- // 方案二:使用redisTemplate操作緩存
- @SuppressWarnings("unchecked")
- ValueOperations<String, Object> valueOper = redisTemplate.opsForValue();
- Object value = valueOper.get(key); // 從緩存獲取數據
- if (value != null) {
- return value; // 如果有數據,則直接返回
- }
- value = pjp.proceed(); // 緩存,到後端查詢數據
- CacheUtils.set(key, value, cache.expire());
- if (cache.expire().getTime() <= 0) { // 如果沒有設置過期時間,則無限期緩存
- valueOper.set(key, value);
- } else { // 否則設置緩存時間
- valueOper.set(key, value, cache.expire().getTime(), TimeUnit.SECONDS);
- }
- return value;
- }
- @SuppressWarnings("unchecked")
- @Around("@annotation(evict)")
- public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict evict) throws Throwable {
- Object value = pjp.proceed(); // 執行方法
- String key = getCacheKey(pjp, evict.key());
- // //方案一:使用自定義緩存工具類操作緩存
- // CacheUtils.del(key);
- // 方案二:使用redisTemplate操作緩存
- if (evict.key().equals(key)) {// 支持批量刪除
- Set<String> keys = redisTemplate.keys(key.concat("*"));
- redisTemplate.delete(keys);
- }else{
- redisTemplate.delete(key);
- }
- return value;
- }
- /**
- * 獲取緩存的key值
- *
- * @param pjp
- * @param key
- * @return
- */
- private String getCacheKey(ProceedingJoinPoint pjp, String key) {
- StringBuilder buf = new StringBuilder();
- Object[] args = pjp.getArgs();
- if(StringUtils.isNotBlank(key)){
- buf.append(key);
- List<String> annoParamNames = AopUtils.getAnnoParams(key);
- String[] methodParamNames = AopUtils.getMethodParamNames(AopUtils.getMethod(pjp));
- if(!CollectionUtils.isEmpty(annoParamNames)){
- for (String ap : annoParamNames) {
- String paramValue = "";
- for (int i = 0; i < methodParamNames.length; i++) {
- if(ap.startsWith(methodParamNames[i])){
- Object arg = args[i];
- if (ap.contains(".")) {
- paramValue = String.valueOf(ReflectionUtils.invokeGetter(arg, ap.substring(ap.indexOf(".") + 1)));
- } else {
- paramValue = String.valueOf(arg);
- }
- }
- }
- int start = buf.indexOf("{" + ap);
- int end = start + ap.length() + 2;
- buf = buf.replace(start, end, paramValue);
- }
- }
- }else{
- buf.append(pjp.getSignature().getDeclaringTypeName()).append(":").append(pjp.getSignature().getName());
- for (Object arg : args) {
- buf.append(":").append(arg.toString());
- }
- }
- return buf.toString();
- }
- }
裏面使用到AopUtils.java和ExpireTime.java兩個類,具體代碼如下:
Java代碼- package com.lh.common.annotation;
- import org.aspectj.lang.ProceedingJoinPoint;
- import org.aspectj.lang.Signature;
- import org.aspectj.lang.reflect.MethodSignature;
- import org.springframework.asm.*;
- import java.io.IOException;
- import java.io.InputStream;
- import java.lang.reflect.Method;
- import java.lang.reflect.Modifier;
- import java.util.ArrayList;
- import java.util.List;
- import java.util.regex.Matcher;
- import java.util.regex.Pattern;
- /**
- * 切面編程工具類
- * @author lh
- * @version 3.0
- * @since 2016-8-26
- */
- public class AopUtils {
- /**
- * <p>獲取方法的參數名</p>
- *
- * @param m
- * @return
- */
- public static String[] getMethodParamNames(final Method m) {
- final String[] paramNames = new String[m.getParameterTypes().length];
- final String n = m.getDeclaringClass().getName();
- final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- String className = m.getDeclaringClass().getSimpleName();
- ClassReader cr = null;
- InputStream resourceAsStream = null;
- try {
- // cr = new ClassReader(n);
- // String filePathName = Class.forName(n).getResource("EDayHqbProcessManagerImpl.class").getPath();
- resourceAsStream = Class.forName(n).getResourceAsStream(className + ".class");
- cr = new ClassReader(resourceAsStream);
- // cr = new ClassReader(ClassLoader.getSystemResourceAsStream(n + ".class"));
- } catch (IOException e) {
- //e.printStackTrace();
- // Exceptions.uncheck(e);
- } catch (ClassNotFoundException e) {
- //e.printStackTrace();
- } finally {
- if (resourceAsStream != null) {
- try {
- resourceAsStream.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- assert cr != null;
- cr.accept(new ClassVisitor(Opcodes.ASM4, cw) {
- @Override
- public MethodVisitor visitMethod(final int access,
- final String name, final String desc,
- final String signature, final String[] exceptions) {
- final Type[] args = Type.getArgumentTypes(desc);
- // 方法名相同並且參數個數相同
- if (!name.equals(m.getName())
- || !sameType(args, m.getParameterTypes())) {
- return super.visitMethod(access, name, desc, signature,
- exceptions);
- }
- MethodVisitor v = cv.visitMethod(access, name, desc, signature,
- exceptions);
- return new MethodVisitor(Opcodes.ASM4, v) {
- @Override
- public void visitLocalVariable(String name, String desc,
- String signature, Label start, Label end, int index) {
- int i = index - 1;
- // 如果是靜態方法,則第一就是參數
- // 如果不是靜態方法,則第一個是"this",然後才是方法的參數
- if (Modifier.isStatic(m.getModifiers())) {
- i = index;
- }
- if (i >= 0 && i < paramNames.length) {
- paramNames[i] = name;
- }
- super.visitLocalVariable(name, desc, signature, start,
- end, index);
- }
- };
- }
- }, 0);
- return paramNames;
- }
- /**
- * <p>比較參數類型是否一致</p>
- *
- * @param types asm的類型({@link Type})
- * @param clazzes java 類型({@link Class})
- * @return
- */
- private static boolean sameType(Type[] types, Class<?>[] clazzes) {
- // 個數不同
- if (types.length != clazzes.length) {
- return false;
- }
- for (int i = 0; i < types.length; i++) {
- if (!Type.getType(clazzes[i]).equals(types[i])) {
- return false;
- }
- }
- return true;
- }
- /**
- * 取得切面調用的方法
- * @param pjp
- * @return
- */
- public static Method getMethod(ProceedingJoinPoint pjp){
- Signature sig = pjp.getSignature();
- MethodSignature msig = null;
- if (!(sig instanceof MethodSignature)) {
- throw new IllegalArgumentException("該註解只能用於方法");
- }
- msig = (MethodSignature) sig;
- Object target = pjp.getTarget();
- Method currentMethod = null;
- try {
- currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
- } catch (NoSuchMethodException e) {
- } catch (SecurityException e) {
- }
- return currentMethod;
- }
- public static List<String> getMatcher(String regex, String source) {
- List<String> list = new ArrayList<String>();
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(source);
- while (matcher.find()) {
- list.add(matcher.group());
- }
- return list;
- }
- /**
- * 取得註解參數
- (?=exp) 匹配exp前面的位置
- (?<=exp) 匹配exp後面的位置
- (?!exp) 匹配後面跟的不是exp的位置
- (?<!exp) 匹配前面不是exp的位置
- * @param managers
- * @return
- */
- public static List<String> getAnnoParams(String source){
- String regex = "(?<=\\{)(.+?)(?=\\})";
- return getMatcher(regex, source);
- }
- }
Java代碼
- package com.lh.common.dict;
- /**
- * 失效時間枚舉類
- * @author lh
- * @version 3.0
- * @since 2016-8-25
- *
- */
- public enum ExpireTime {
- /**
- * 無固定期限
- */
- NONE(0, "無固定期限")
- /**
- * 1秒鐘
- */
- ,ONE_SEC(1, "1秒鐘")
- /**
- * 5秒鐘
- */
- ,FIVE_SEC(5, "5秒鐘")
- /**
- * 10秒鐘
- */
- ,TEN_SEC(10, "10秒鐘")
- /**
- * 30秒鐘
- */
- ,HALF_A_MIN(30, "30秒鐘")
- /**
- * 1分鐘
- */
- ,ONE_MIN(60, "1分鐘")
- /**
- * 5分鐘
- */
- ,FIVE_MIN(5 * 60, "5分鐘")
- /**
- * 10分鐘
- */
- ,TEN_MIN(10 * 60, "10分鐘")
- /**
- * 20分鐘
- */
- ,TWENTY_MIN(20 * 60, "20分鐘")
- /**
- * 30分鐘
- */
- ,HALF_AN_HOUR(30 * 60, "30分鐘")
- /**
- * 1小時
- */
- ,ONE_HOUR(60 * 60, "1小時")
- /**
- * 1天
- */
- ,ONE_DAY(24 * 60 * 60, "1天")
- /**
- * 1個月
- */
- ,ONE_MON(30 * 24 * 60 * 60, "1個月")
- /**
- * 1年
- */
- ,ONE_YEAR(365 * 24 * 60 * 60, "1年")
- ;
- /**
- * 時間
- */
- private final int time;
- /**
- * 描述
- */
- private final String desc;
- ExpireTime(int time, String desc) {
- this.time = time;
- this.desc = desc;
- }
- /**
- * 獲取具體時間
- * @return
- */
- public int getTime() {
- return time;
- }
- /**
- * 獲取時間描述信息
- * @return
- */
- public String getDesc() {
- return desc;
- }
- /**
- * 根據時間匹配失效期限
- * @param time
- * @return
- */
- public static ExpireTime match(int time){
- if(NONE.getTime() == time){
- return NONE;
- }else if(ONE_SEC.getTime() == time){
- return ONE_SEC;
- }else if(FIVE_SEC.getTime() == time){
- return FIVE_SEC;
- }else if(TEN_SEC.getTime() == time){
- return TEN_SEC;
- }else if(HALF_A_MIN.getTime() == time){
- return HALF_A_MIN;
- }else if(ONE_MIN.getTime() == time){
- return ONE_MIN;
- }else if(FIVE_MIN.getTime() == time){
- return FIVE_MIN;
- }else if(TEN_MIN.getTime() == time){
- return TEN_MIN;
- }else if(TWENTY_MIN.getTime() == time){
- return TWENTY_MIN;
- }else if(HALF_AN_HOUR.getTime() == time){
- return HALF_AN_HOUR;
- }else if(ONE_HOUR.getTime() == time){
- return ONE_HOUR;
- }else if(ONE_DAY.getTime() == time){
- return ONE_DAY;
- }else if(ONE_MON.getTime() == time){
- return ONE_MON;
- }else if(ONE_YEAR.getTime() == time){
- return ONE_YEAR;
- }
- return HALF_AN_HOUR;
- }
- }
配置中的RdRedisCache.java 代碼如下:
Java代碼
- package com.lh.common.jedis;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.ObjectOutputStream;
- import net.sf.ehcache.Element;
- import org.springframework.cache.Cache;
- import org.springframework.cache.support.SimpleValueWrapper;
- import org.springframework.dao.DataAccessException;
- import org.springframework.data.redis.connection.RedisConnection;
- import org.springframework.data.redis.core.RedisCallback;
- import org.springframework.data.redis.core.RedisTemplate;
- public class RdRedisCache implements Cache {
- private RedisTemplate<String, Object> redisTemplate;
- private String name;
- public RedisTemplate<String, Object> getRedisTemplate() {
- return redisTemplate;
- }
- public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
- this.redisTemplate = redisTemplate;
- }
- public void setName(String name) {
- this.name = name;
- }
- @Override
- public String getName() {
- return this.name;
- }
- @Override
- public Object getNativeCache() {
- return this.redisTemplate;
- }
- @Override
- public ValueWrapper get(Object key) {
- final String keyf = obj2Str(key);
- Object object = null;
- object = redisTemplate.execute(new RedisCallback<Object>() {
- public Object doInRedis(RedisConnection connection)
- throws DataAccessException {
- byte[] key = keyf.getBytes();
- byte[] value = connection.get(key);
- if (value == null) {
- return null;
- }
- return toObject(value);
- }
- });
- return (object != null ? new SimpleValueWrapper(object) : null);
- }
- @Override
- public void put(Object key, Object value) {
- final String keyf = obj2Str(key);
- final Object valuef = value;
- final long liveTime = 86400;
- redisTemplate.execute(new RedisCallback<Long>() {
- public Long doInRedis(RedisConnection connection)
- throws DataAccessException {
- byte[] keyb = keyf.getBytes();
- byte[] valueb = toByteArray(valuef);
- connection.set(keyb, valueb);
- if (liveTime > 0) {
- connection.expire(keyb, liveTime);
- }
- return 1L;
- }
- });
- }
- public String obj2Str(Object key){
- String keyStr = null;
- if(key instanceof Integer){
- keyStr = ((Integer)key).toString();
- }else if(key instanceof Long){
- keyStr = ((Long)key).toString();
- }else {
- keyStr = (String)key;
- }
- return keyStr;
- }
- /**
- * 描述 : <Object轉byte[]>. <br>
- * <p>
- * <使用方法說明>
- * </p>
- *
- * @param obj
- * @return
- */
- private byte[] toByteArray(Object obj) {
- byte[] bytes = null;
- ByteArrayOutputStream bos = new ByteArrayOutputStream();
- try {
- ObjectOutputStream oos = new ObjectOutputStream(bos);
- oos.writeObject(obj);
- oos.flush();
- bytes = bos.toByteArray();
- oos.close();
- bos.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- }
- return bytes;
- }
- /**
- * 描述 : <byte[]轉Object>. <br>
- * <p>
- * <使用方法說明>
- * </p>
- *
- * @param bytes
- * @return
- */
- private Object toObject(byte[] bytes) {
- Object obj = null;
- try {
- ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
- ObjectInputStream ois = new ObjectInputStream(bis);
- obj = ois.readObject();
- ois.close();
- bis.close();
- } catch (IOException ex) {
- ex.printStackTrace();
- } catch (ClassNotFoundException ex) {
- ex.printStackTrace();
- }
- return obj;
- }
- @Override
- public void evict(Object key) {
- final String keyf = obj2Str(key);
- redisTemplate.execute(new RedisCallback<Long>() {
- public Long doInRedis(RedisConnection connection)
- throws DataAccessException {
- return connection.del(keyf.getBytes());
- }
- });
- }
- @Override
- public void clear() {
- redisTemplate.execute(new RedisCallback<String>() {
- public String doInRedis(RedisConnection connection)
- throws DataAccessException {
- connection.flushDb();
- return "ok";
- }
- });
- }
- @Override
- public <T> T get(Object key, Class<T> type) {
- ValueWrapper wrapper = get(key);
- return wrapper == null ? null : (T) wrapper.get();
- }
- @Override
- public ValueWrapper putIfAbsent(Object key, Object value) {
- synchronized (key) {
- ValueWrapper wrapper = get(key);
- if (wrapper != null) {
- return wrapper;
- }
- put(key, value);
- return toWrapper(new Element(key, value));
- }
- }
- private ValueWrapper toWrapper(Element element) {
- return (element != null ? new SimpleValueWrapper(element.getObjectValue()) : null);
- }
- }
spring配置文件的相關配置如下:
Xml代碼
- <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
- <property name="maxIdle" value="${redis.pool.maxIdle}" /> <!-- 最大能夠保持idel狀態的對象數 -->
- <property name="maxTotal" value="${redis.pool.maxTotal}" /> <!-- 最大分配的對象數 -->
- <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <!-- 當調用borrow Object方法時,是否進行有效性檢查 -->
- </bean>
- <!-- jedisPool init -->
- <bean id="jedisPool" class="redis.clients.jedis.JedisPool">
- <constructor-arg index="0" ref="jedisPoolConfig" />
- <constructor-arg index="1" value="${redis.host}" type="String" />
- <constructor-arg index="2" value="${redis.port}" type="int" />
- </bean>
- <!-- jedis單機配置 -->
- <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" >
- <property name="hostName" value="${redis.host}" />
- <property name="port" value="${redis.port1}" />
- <property name="timeout" value="${redis.timeout}" />
- <property name="poolConfig" ref="jedisPoolConfig" />
- </bean>
- <bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
- <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
- p:connectionFactory-ref="jedisConnFactory" p:keySerializer-ref="stringRedisSerializer" />
- <!-- spring自己的緩存管理器 -->
- <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
- <property name="caches">
- <set>
- <bean class="com.lh.common.jedis.RdRedisCache" p:redis-template-ref="redisTemplate" p:name="sysCache"/>
- </set>
- </property>
- </bean>
- <!-- 啟用緩存註解功能,這個是必須的,否則註解不會生效,另外,該註解一定要聲明在spring主配置文件中才會生效 -->
- <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true" />
spring-data-redis時效設置