package com.qxueyou.scc.base.handler; import java.io.ByteArrayOutputStream; import java.io.OutputStreamWriter; import java.io.Reader; import java.io.StringReader; import java.io.Writer; import java.util.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.annotation.PostConstruct; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.annotation.AnnotationUtils; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import com.qxueyou.scc.base.model.CacheConstants; import com.qxueyou.scc.base.service.ICacheService; import com.qxueyou.scc.base.util.ClientUtils; import com.qxueyou.scc.base.util.FreeMarkerMd5MethodDefine; import freemarker.template.Configuration; import freemarker.template.Template; import reactor.util.IoUtils; @Order(100) @Aspect @Component public class QCacheMonitor { private static final Logger logger = LogManager.getLogger("QCacheMonitor"); private static int MAX_KEY_LENGTH = 250; private static FreeMarkerMd5MethodDefine md5MethodObj = new FreeMarkerMd5MethodDefine(); private static Pattern argPattern = Pattern.compile("\\$\\{arg([0-9])\\}"); //判断当前线程是否已经执行过目标方法的标志 private final ThreadLocal> targetMethodResult = new ThreadLocal>(); private Configuration cfg; /** 缓存 **/ @Autowired ICacheService cache; @Autowired QCacheRedis qCacheRedis; @Pointcut("execution(@com.iqtogether.qxueyou..QCache* * *(..))") public void pointCutMethod() { } @PostConstruct private void initFreeMarkerCfg() throws Exception { cfg = new Configuration(Configuration.VERSION_2_3_21); cfg.setTemplateUpdateDelay(10); cfg.setCacheStorage(new freemarker.cache.MruCacheStorage(1000, 200)); cfg.setDefaultEncoding("UTF-8"); cfg.setLocale(java.util.Locale.CHINA); cfg.setClassicCompatible(true); } @Around("pointCutMethod()" ) public Object doAround(ProceedingJoinPoint pjp) throws Throwable { MethodSignature methodSignature = null; QCacheReader annoQCacheReader = null; QCacheCleaner annoQCacheCleaner = null; String excuteMethodKey = null; try { //test(); methodSignature = (MethodSignature) pjp.getSignature(); excuteMethodKey = this.getExecuteMethodKey(pjp); //读取缓存的注解 annoQCacheReader = AnnotationUtils.findAnnotation(methodSignature.getMethod(), QCacheReader.class); //清理缓存注解 annoQCacheCleaner = AnnotationUtils.findAnnotation(methodSignature.getMethod(), QCacheCleaner.class); if(annoQCacheReader!=null && annoQCacheCleaner==null){ this.processReadCache(pjp, annoQCacheReader, methodSignature); } if(annoQCacheReader==null && annoQCacheCleaner!=null){ this.processCleanCache(pjp, annoQCacheCleaner, methodSignature); } if(annoQCacheReader!=null && annoQCacheCleaner!=null){ this.processCleanCache(pjp, annoQCacheCleaner, methodSignature); this.processReadCache(pjp, annoQCacheReader, methodSignature); } } catch (Exception e) { logger.error("缓存处理失败,msg=" + e.getMessage(), e); } return clearAndReturnMethodResult(pjp,excuteMethodKey); } private void processReadCache(ProceedingJoinPoint pjp, QCacheReader qCacheReader, MethodSignature methodSignature) throws Exception { Object result = null; String strCacheKey = null; int expireTime = 0; try { expireTime = this.getCacheTimeSecond(qCacheReader); strCacheKey = this.checkAndGenerateCacheKey(pjp.getTarget().getClass(), qCacheReader.cacheKey(), methodSignature, converToParamMap(pjp.getArgs())); //执行方法对应的key值 String excuteMethodKey = this.getExecuteMethodKey(pjp); if (StringUtils.isNotEmpty(strCacheKey)) { String strBucketKey = qCacheReader.bucket(); if (StringUtils.isEmpty(strBucketKey)) { strBucketKey = strCacheKey; } result = qCacheRedis.annocacheHget(CacheConstants.QXY_SERVICE_CACHE_NS + strBucketKey, strCacheKey, methodSignature.getMethod().getReturnType()); if (result == null) { executeTargetMethod(pjp,excuteMethodKey); if (targetMethodResult.get()!=null && targetMethodResult.get().containsKey(excuteMethodKey)) { qCacheRedis.annocacheHset(CacheConstants.QXY_SERVICE_CACHE_NS + strBucketKey, strCacheKey, targetMethodResult.get().get(excuteMethodKey), expireTime); } }else{ //如果result存在,则直接放入targetMethodResult中 Map map = new HashMap(); map.put(excuteMethodKey, result); targetMethodResult.set(map); } } else { executeTargetMethod(pjp,excuteMethodKey); } } catch (Throwable e) { throw new Exception("添加缓存发生异常,strCacheKey=" + strCacheKey, e); } } private void processCleanCache(ProceedingJoinPoint pjp, QCacheCleaner qCacheCleaner, MethodSignature methodSignature) throws Exception { String[] strClearBucketArr = null; String clearBucket = null; try { clearBucket = this.checkAndGenerateCacheKey(pjp.getTarget().getClass(), qCacheCleaner.clearBucket(), methodSignature, converToParamMap(pjp.getArgs())); if (StringUtils.isNotEmpty(clearBucket)){ if(StringUtils.isNotEmpty(qCacheCleaner.bucketSplitReg())) { strClearBucketArr = clearBucket.split(qCacheCleaner.bucketSplitReg()); }else{ strClearBucketArr =new String[]{clearBucket}; } } if (strClearBucketArr != null && strClearBucketArr.length > 0) { for (int i = 0; i < strClearBucketArr.length; i++) { strClearBucketArr[i] = CacheConstants.QXY_SERVICE_CACHE_NS.concat(strClearBucketArr[i]); } logger.info("清理的bucket:"+ArrayUtils.toString(strClearBucketArr)); this.qCacheRedis.del(strClearBucketArr); } } catch (Throwable e) { logger.error("清理缓存发生异常,strClearBucketArr" + ArrayUtils.toString(strClearBucketArr, ""), e); } } private String checkAndGenerateCacheKey(Class targetClass, String key, MethodSignature methodSignature, Map paramMap) throws Exception { String strNewKey = null; if (StringUtils.isNotEmpty(key)) { logger.info("处理前的key:" + key); // 对部分参数进行替换处理 strNewKey = replaceArgInfo(key, paramMap); // freeMarker解析cackeKey strNewKey = parseCacheKey(strNewKey, targetClass, methodSignature, paramMap); logger.info("处理后的key:" + strNewKey); if (strNewKey.length() > MAX_KEY_LENGTH) { throw new Exception("Service缓存时Key过长,大于250个字符"); } } return strNewKey; } private String parseCacheKey(String strCacheKey, Class targetClass, MethodSignature methodSignature, Map paramMap) throws Exception { Map data = null; Reader in = null; String templateName = null; Template template = null; ByteArrayOutputStream stream = null; Writer out = null; try { data = new HashMap(); if(paramMap!=null){ data.putAll(paramMap); } data.put("md5", md5MethodObj); data.put("clzName", targetClass.getName()); data.put("methodName", methodSignature.getMethod().getName()); data.put("classId", ClientUtils.getClassId()); data.put("userId", ClientUtils.getUserId()); data.put("orgId", ClientUtils.getOrgId()); in = new StringReader(strCacheKey); templateName = targetClass.getName().concat(".").concat(methodSignature.getMethod().getName()); template = new Template(templateName, in, cfg); stream = new ByteArrayOutputStream(); out = new OutputStreamWriter(stream); template.process(data, out); out.flush(); return new String(stream.toByteArray(), "UTF-8"); } catch (Exception e) { logger.error("使用freemark解析cacheKey失败", e); throw e; } finally { IoUtils.closeQuietly(stream); IoUtils.closeQuietly(out); } } private String replaceArgInfo(String strCacheKey, Map paramMap) throws Exception { StringBuffer sbBuffer = new StringBuffer(1024); Matcher matcher = null; String strParamName = null; Class paramClz = null; logger.info("替换前的key:" + strCacheKey); if (StringUtils.isNotEmpty(strCacheKey) && paramMap != null && !paramMap.isEmpty()) { matcher = argPattern.matcher(strCacheKey); while (matcher.find()) { strParamName = "arg" + matcher.group(1); // 参数类型判断 if (paramMap.containsKey(strParamName)) { if(paramMap.get(strParamName)!=null){ paramClz = paramMap.get(strParamName).getClass(); if (paramClz.isAssignableFrom(java.lang.Object.class)) { throw new Exception("错误:缓存参数类型不能是Object.class类型的对象"); } else if (paramClz.isAssignableFrom(java.util.List.class) || paramClz.isArray()) { matcher.appendReplacement(sbBuffer, "<#list " + strParamName + " as node>_${node}"); } else if (paramClz.isAssignableFrom(java.util.Map.class)) { matcher.appendReplacement(sbBuffer, "<#list " + strParamName + "?keys as key>_${key}:${" + strParamName + "['${key}']}"); } else if (paramClz.isAssignableFrom(java.util.Date.class)) { matcher.appendReplacement(sbBuffer, "${date?datetime}"); } } } else { throw new Exception("错误:参数表达式中存在不合法的参数"); } } matcher.appendTail(sbBuffer); logger.info("替换后的key:" + sbBuffer.toString()); return sbBuffer.toString(); }else{ return strCacheKey; } } private Map converToParamMap(Object[] paramObjects) { Map paramMap = null; if (paramObjects != null && paramObjects.length > 0) { paramMap = new HashMap(paramObjects.length); for (int i = 0; i < paramObjects.length; i++) { paramMap.put("arg" + i, paramObjects[i]); } } return paramMap; } private int getCacheTimeSecond(QCacheReader cacheAnno) throws Exception { int expireTime = 0; switch (cacheAnno.timeUnit()) { case MINUTE: expireTime = cacheAnno.cacheTime() * 60; break; case HOUR: expireTime = cacheAnno.cacheTime() * 60 * 60; break; case DAY: expireTime = cacheAnno.cacheTime() * 24 * 60 * 60; break; default: expireTime = cacheAnno.cacheTime(); break; } return expireTime; } /** * 执行目标方法,只允许执行一次,并保存当前结果 * @param pjp * @param key * @throws Throwable */ private void executeTargetMethod(ProceedingJoinPoint pjp ,String key) throws Throwable{ String excuteMethodKey= key; if(StringUtils.isEmpty(excuteMethodKey)){ excuteMethodKey = this.getExecuteMethodKey(pjp); } //是否需要判断是否是void方法 if(targetMethodResult.get()==null || !targetMethodResult.get().containsKey(excuteMethodKey)){ Map map = new HashMap(); map.put(excuteMethodKey, pjp.proceed()); targetMethodResult.set(map); } } /** * 返回并清理ThreadLocal>的MAP中的KEY * @param pjp * @param key * @throws Throwable */ private Object clearAndReturnMethodResult(ProceedingJoinPoint pjp ,String key) throws Throwable{ Object result = null; String excuteMethodKey= key; if(StringUtils.isEmpty(excuteMethodKey)){ excuteMethodKey = getExecuteMethodKey(pjp); } //是否需要判断是否是void方法(TODO) if(targetMethodResult.get()!=null && targetMethodResult.get().containsKey(excuteMethodKey)){ result =targetMethodResult.get().get(excuteMethodKey); targetMethodResult.get().remove(excuteMethodKey); }else{ result = pjp.proceed(); } return result; } /** * 返回当前目标方法的执行结果存储在ThreadLocal>的MAP中的KEY * @param pjp * @return key */ private String getExecuteMethodKey(ProceedingJoinPoint pjp){ MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); StringBuffer sBuffer = new StringBuffer(512); sBuffer.append(Thread.currentThread().getId()); sBuffer.append('.'); sBuffer.append(pjp.getTarget().getClass().getName()); sBuffer.append('.'); sBuffer.append(methodSignature.getMethod().getName()); return sBuffer.toString(); } // public void test() { // // ExecutorService es = null; // // try { // int j =0; // int size = 1000; // while (size>0) { // long start = System.currentTimeMillis(); // ++j; // size=size-50; // // this.annoCacheRedis.del("testhget"); // es = Executors.newFixedThreadPool(1000); // System.out.println("testReisConn"); // for (int i = 0; i < 1000; i++) { // es.execute(new TestThread()); // } // es.shutdown(); // if(es.awaitTermination(1, java.util.concurrent.TimeUnit.HOURS)){ // System.out.println("线程执行完成"); // } // long end = System.currentTimeMillis(); // System.out.println(start-end); // } // }catch (Exception e) { // // TODO Auto-generated catch block // e.printStackTrace(); // } // // } // // // static class TestThread implements Runnable{ // public void run() { // CommonRedisTemplate commonTemplate = null; // ShardedJedis sjedisJedis = null; // try { // commonTemplate = SpringUtil.getBean("commonRedisTemplate",CommonRedisTemplate.class); // for(int i=0;i<1;i++){ // sjedisJedis = commonTemplate.getJedisClient(); // sjedisJedis.incr("testincr1"); // sjedisJedis.hset("testhget", String.valueOf(Thread.currentThread().getName().concat(String.valueOf(i))), "1234567890"); // sjedisJedis.hget("testhget", String.valueOf(Thread.currentThread().getName().concat(String.valueOf(i)))); // } // } catch (Exception e) { // e.printStackTrace(); // }finally { // commonTemplate.closeJedisClient(sjedisJedis); // } // } // // } }