`
hbxflihua
  • 浏览: 660085 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

spring-data-redis 扩展实现时效设置

阅读更多

spring目前在@Cacheable和@CacheEvict等注解上不支持缓存时效设置,只允许通过配置文件设置全局时效。这样就很不方便设定时间。比如系统参数和业务数据的时效是不一样的,这给程序开发造成很大的困扰。不得已,我重写了spring的这两个注解。以下是具体实现。

 

首先定义@Cacheable和@CacheEvict注解类。

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; // 缓存时效,默认无限期

}

 

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)如下:

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两个类,具体代码如下:

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);
    }
	

}

 

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 代码如下:

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配置文件的相关配置如下:

 
 	<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" />
	

 

 

 The end!

 

 

分享到:
评论
5 楼 yaoyao66123 2017-03-14  
com.rd.ifaes.common.util 这个包下的工具类在哪找到?
4 楼 q13073451412 2017-03-13  
q13073451412 写道
原来就支持自定义不同时长的cache的吧
配置cachemanager
 <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
          c:template-ref="redisTemplate" p:defaultExpiration="#{60*60*12}" p:usePrefix="true">
        <property name="expires">
            <util:map>
                <entry value="43200">
                    <key>remind</key>
                </entry>
                <entry value="180000">
                    <key>menuByUrl</key>
                </entry>
            </util:map>
        </property>
    </bean>

详细见
 <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
          c:template-ref="redisTemplate" p:defaultExpiration="#{60*60*12}" p:usePrefix="true">
        <property name="expires">
            <util:map>
                <entry value="43200">
                    <key>remind</key>
                </entry>
                <entry value="180000">
                    <key>menuByUrl</key>
                </entry>
            </util:map>
        </property>
    </bean>


xml写的不对不好意思
<bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
          c:template-ref="redisTemplate" p:defaultExpiration="#{60*60*12}" p:usePrefix="true">
        <property name="expires">
            <util:map>
                <entry value="43200" key="remind"/>
                <entry value="180000" key="menuByUrl"/>
            </util:map>
        </property>
    </bean>
3 楼 q13073451412 2017-03-13  
原来就支持自定义不同时长的cache的吧
配置cachemanager
 <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
          c:template-ref="redisTemplate" p:defaultExpiration="#{60*60*12}" p:usePrefix="true">
        <property name="expires">
            <util:map>
                <entry value="43200">
                    <key>remind</key>
                </entry>
                <entry value="180000">
                    <key>menuByUrl</key>
                </entry>
            </util:map>
        </property>
    </bean>

详细见
 <bean id="cacheManager" class="org.springframework.data.redis.cache.RedisCacheManager"
          c:template-ref="redisTemplate" p:defaultExpiration="#{60*60*12}" p:usePrefix="true">
        <property name="expires">
            <util:map>
                <entry value="43200">
                    <key>remind</key>
                </entry>
                <entry value="180000">
                    <key>menuByUrl</key>
                </entry>
            </util:map>
        </property>
    </bean>
2 楼 goahead2010 2017-01-23  
我看没生效也是这样,更新下spring boot版本试试
1 楼 javazone 2016-12-14  
牛X,我服。

相关推荐

Global site tag (gtag.js) - Google Analytics