基于spring的缓存注解实现
程序员文章站
2022-07-13 08:54:52
...
时间原因,这里只贴代码,见谅。
package com.rd.ifaes.common.annotation; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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 { private static final Logger LOGGER = LoggerFactory.getLogger(AopUtils.class); private static final String DESC_DOUBLE = "D"; private static final String DESC_SHORT = "J"; private 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 { resourceAsStream = Class.forName(n).getResourceAsStream(className + ".class"); cr = new ClassReader(resourceAsStream); } catch (IOException | ClassNotFoundException e) { LOGGER.warn(e.getMessage(), e); } finally { if (resourceAsStream != null) { try { resourceAsStream.close(); } catch (IOException e) { LOGGER.warn(e.getMessage(), e); } } } if (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) { int fixCount = 0;//步长修正计数器 @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 > fixCount) { i -= fixCount; } if(desc.equals(DESC_SHORT) || desc.equals(DESC_DOUBLE)){ fixCount++; } 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(); if (!(sig instanceof MethodSignature)) { throw new IllegalArgumentException("该注解只能用于方法"); } MethodSignature msig = (MethodSignature) sig; Object target = pjp.getTarget(); Method currentMethod = null; try { currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes()); } catch (NoSuchMethodException | SecurityException e) { LOGGER.warn(e.getMessage(), e); } return currentMethod; } public static List<String> getMatcher(String regex, String source) { List<String> list = new ArrayList<>(); 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.rd.ifaes.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; /** * 添加缓存 * @author lh * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Cacheable { /** * 缓存key * @return */ public String key() default ""; /** * 缓存时效,默认无限期 * @return */ public ExpireTime expire() default ExpireTime.NONE; }
package com.rd.ifaes.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; import com.rd.ifaes.common.dict.ExpireTime; /** * 缓存清除 * @author lh * @version 3.0 * @since 2016-8-28 * */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheEvict { /** * 缓存key * @return */ String key() default ""; /** * 缓存key数组 * @return */ String[] keys() default{}; /** * 操作之间的缓存时间(秒) * @author FangJun * @date 2016年9月9日 * @return 默认0,不做限制 */ ExpireTime interval() default ExpireTime.NONE; }
package com.rd.ifaes.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.slf4j.Logger; import org.slf4j.LoggerFactory; 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.dict.ExpireTime; import com.rd.ifaes.common.util.ReflectionUtils; import com.rd.ifaes.common.util.StringUtils; import com.rd.ifaes.core.core.constant.CacheConstant; import com.rd.ifaes.core.core.constant.Constant; import com.rd.ifaes.core.core.util.CacheUtils; /** * 缓存操作切面 * @author lh * */ @Aspect @Component public class CacheAspect { private static final Logger LOGGER = LoggerFactory.getLogger(CacheAspect.class); @SuppressWarnings("rawtypes") @Autowired private RedisTemplate redisTemplate; /** * 添加缓存 * @param pjp * @param cache * @return * @throws Throwable */ @Around("@annotation(cache)") public Object cacheable(final ProceedingJoinPoint pjp, Cacheable cache)throws Throwable { String key = getCacheKey(pjp, cache.key()); //使用redisTemplate操作缓存 @SuppressWarnings("unchecked") ValueOperations<String, Object> valueOper = redisTemplate.opsForValue(); Object value = valueOper.get(key); // 从缓存获取数据 if (value != null) { return value; // 如果有数据,则直接返回 } value = pjp.proceed(); if(LOGGER.isInfoEnabled()){ LOGGER.info("cachePut, key={}", key); } // 缓存,到后端查询数据 if (cache.expire().getTime() <= 0) { // 如果没有设置过期时间,则无限期缓存 valueOper.set(key, value); } else { // 否则设置缓存时间 valueOper.set(key, value, cache.expire().getTime(), TimeUnit.SECONDS); } return value; } @Around("@annotation(evict)") public Object cacheEvict(final ProceedingJoinPoint pjp, CacheEvict evict)throws Throwable { Object value; // 执行方法 value = pjp.proceed(); //单个key操作 if (StringUtils.isNotBlank(evict.key())) { String keyname = evict.key(); evictByKeyname(pjp, keyname,evict.interval()); } //批量key操作 if (evict.keys() != null && evict.keys().length > 0) { for (String keyname : evict.keys()) { evictByKeyname(pjp, keyname,evict.interval()); } } return value; } @SuppressWarnings("unchecked") private void evictByKeyname(final ProceedingJoinPoint pjp, final String keyname, ExpireTime interval) { final String key = getCacheKey(pjp, keyname); //操作间隔判断 if (!ExpireTime.NONE.equals(interval)) { final String intervalKey = CacheConstant.KEY_PREFIX_CACHE_EVICT + key; if (CacheUtils.incr(intervalKey, Constant.DOUBLE_ONE) > Constant.DOUBLE_ONE) { return; } CacheUtils.expire(intervalKey, interval); } if(LOGGER.isInfoEnabled()){ LOGGER.info("cacheEvict, key={}", key); } //使用redisTemplate操作缓存 if (keyname.equals(key)) {// 支持批量删除 Set<String> keys = redisTemplate.keys(key.concat("*")); if (!CollectionUtils.isEmpty(keys)) { redisTemplate.delete(keys); } } else { redisTemplate.delete(key); } } /** * 获取缓存的key值 * * @param pjp * @param key * @return */ private String getCacheKey(final ProceedingJoinPoint pjp, final String key) { StringBuilder buf = new StringBuilder(); final 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) { buf = replaceParam(buf, args, methodParamNames, ap); } } }else{ buf.append(pjp.getSignature().getDeclaringTypeName()).append(":").append(pjp.getSignature().getName()); for (Object arg : args) { buf.append(":").append(arg.toString()); } } return buf.toString(); } /** * 替换占位参数 * @param buf * @param args * @param methodParamNames * @param ap * @return */ private StringBuilder replaceParam(StringBuilder buf, final Object[] args, String[] methodParamNames, String ap) { StringBuilder builder = new StringBuilder(buf); String paramValue = ""; for (int i = 0; i < methodParamNames.length; i++) { if(ap.startsWith(methodParamNames[i])){ final Object arg = args[i]; if (ap.contains(".")) { paramValue = String.valueOf(ReflectionUtils.invokeGetter(arg, ap.substring(ap.indexOf('.') + 1))); } else { paramValue = String.valueOf(arg); } break; } } int start = builder.indexOf("{" + ap); int end = start + ap.length() + 2; builder =builder.replace(start, end, paramValue); return builder; } }
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> <!-- sprin_data_redis 单机配置 --> <bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" > <property name="hostName" value="${redis.host}" /> <property name="port" value="${redis.port}" /> <property name="timeout" value="${redis.timeout}" /> <property name="password" value="${redis.password}" /> <property name="poolConfig" ref="jedisPoolConfig" /> </bean> <!-- key序列化 --> <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.rd.ifaes.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" key-generator="rdKeyGenerator"/> <!-- 自定义主键生成策略 --> <bean id="rdKeyGenerator" class="com.rd.ifaes.common.jedis.RdKeyGenerator"/>