For time reasons, only the code is posted here, sorry.
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; /** * Aspect programming tool class * @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>Get the parameter name of the method</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); // same method name and same number of parameters 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;//Step correction counter @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { int i = index - 1; // If it is a static method, the first is the parameter // If it's not a static method, the first one is "this", then the method parameter 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>Compare parameter types for consistency</p> * * @param types asm's type ({@link Type}) * @param clazzes java type({@link Class}) * @return */ private static boolean sameType(Type[] types, Class<?>[] clazzes) { // different numbers 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; } /** * Get the method called by the aspect * @param pjp * @return */ public static Method getMethod(ProceedingJoinPoint pjp){ Signature sig = pjp.getSignature (); if (!(sig instanceof MethodSignature)) { throw new IllegalArgumentException("This annotation can only be used on methods"); } 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); Matches matches = pattern.matcher (source); while (matcher.find()) { list.add(matcher.group()); } return list; } /** * Get annotation parameters (?=exp) matches the position before exp (?<=exp) matches the position after exp (?!exp) matches a position that is not followed by exp (?<!exp) matches a position not preceded by 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; /** * add cache * @author lh * */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface Cacheable { /** * cache key * @return */ public String key() default ""; /** * Cache aging, default indefinitely * @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; /** * Cache clear * @author lh * @version 3.0 * @since 2016-8-28 * */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface CacheEvict { /** * cache key * @return */ String key() default ""; /** * Cache key array * @return */ String[] keys() default{}; /** * Cache time between operations (seconds) * @author FangJun * @date September 9, 2016 * @return default 0, no limit */ 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; /** * Cache operation aspect * @author lh * */ @Aspect @Component public class CacheAspect { private static final Logger LOGGER = LoggerFactory.getLogger(CacheAspect.class); @SuppressWarnings("rawtypes") @Autowired private RedisTemplate redisTemplate; /** * add cache * @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 ()); //Use redisTemplate to operate the cache @SuppressWarnings("unchecked") ValueOperations<String, Object> valueOper = redisTemplate.opsForValue(); Object value = valueOper.get(key); // Get data from cache if (value != null) { return value; // if there is data, return directly } value = pjp.proceed(); if(LOGGER.isInfoEnabled()){ LOGGER.info("cachePut, key={}", key); } // Cache, query data to the backend if (cache.expire().getTime() <= 0) { // If no expiration time is set, cache indefinitely valueOper.set(key, value); } else { // Otherwise set the cache time 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; // execute method value = pjp.proceed(); //Single key operation if (StringUtils.isNotBlank(evict.key())) { String keyname = evict.key(); evictByKeyname(pjp, keyname,evict.interval()); } //Batch key operation 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); //Operation interval judgment 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); } //Use redisTemplate to operate the cache if (keyname.equals(key)) {// Support batch deletion Set<String> keys = redisTemplate.keys(key.concat("*")); if (!CollectionUtils.isEmpty(keys)) { redisTemplate.delete(keys); } } else { redisTemplate.delete(key); } } /** * Get the cached key value * * @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(); } /** * replace placeholder parameters * @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; } }
The spring related configuration is as follows:
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig"> <property name="maxIdle" value="${redis.pool.maxIdle}" /> <!-- The maximum number of objects that can hold the idle state--> <property name="maxTotal" value="${redis.pool.maxTotal}" /> <!-- maximum number of objects allocated --> <property name="testOnBorrow" value="${redis.pool.testOnBorrow}" /> <!-- When calling the borrow Object method, whether to check the validity --> </bean> <!-- sprin_data_redis stand-alone configuration--> <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 serialization--> <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's own cache manager --> <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> <!-- Enable the cache annotation function, this is required, otherwise the annotation will not take effect, in addition, the annotation must be declared in the spring main configuration file to take effect --> <cache:annotation-driven cache-manager="cacheManager" proxy-target-class="true" key-generator="rdKeyGenerator"/> <!-- Custom primary key generation strategy--> <bean id="rdKeyGenerator" class="com.rd.ifaes.common.jedis.RdKeyGenerator"/>