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<Map<String, Object>> targetMethodResult = new ThreadLocal<Map<String, Object>>();
|
|
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<String,Object> map = new HashMap<String,Object>();
|
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<String, Object> 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<String, Object> paramMap) throws Exception {
|
Map<String, Object> data = null;
|
Reader in = null;
|
String templateName = null;
|
Template template = null;
|
ByteArrayOutputStream stream = null;
|
Writer out = null;
|
try {
|
data = new HashMap<String, Object>();
|
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<String, Object> 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}</#list>");
|
} else if (paramClz.isAssignableFrom(java.util.Map.class)) {
|
matcher.appendReplacement(sbBuffer, "<#list " + strParamName + "?keys as key>_${key}:${"
|
+ strParamName + "['${key}']}</#list>");
|
} 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<String, Object> converToParamMap(Object[] paramObjects) {
|
Map<String, Object> paramMap = null;
|
if (paramObjects != null && paramObjects.length > 0) {
|
paramMap = new HashMap<String, Object>(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<String,Object> map = new HashMap<String,Object>();
|
map.put(excuteMethodKey, pjp.proceed());
|
targetMethodResult.set(map);
|
}
|
}
|
|
/**
|
* 返回并清理ThreadLocal<Map<String, Object>>的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<String, Object>>的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);
|
// }
|
// }
|
//
|
// }
|
|
|
|
}
|