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!