自用好几年的redis缓存标签,已经优化过N版,支持并发,拷进项目就能用,不用谢我
原理
拦截器读取方法的参数列表和参数值, 并以方法名和参数组合起来做为redis的key, 定期存取redis来降低数据库的读压力
一个类,一个标签,直接上代码
/**
* @Author: Fcx
* @Date: 2020/4/21 14:30
* @Version 1.0
*/
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
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.LocalVariableTableParameterNameDiscoverer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
* @Author: Fcx
* @Date: 2020/4/21 14:30
* @Version 1.0
*/
@Aspect
@Component
@Slf4j
public class CacheServiceAspect {
@Pointcut("@annotation(com.xxxxx.configurer.cache.UseCache)")
public void dealCacheServiceCut() {
}
@Autowired
private RedisTemplate redisTemplate;
@Around(value = "dealCacheServiceCut()")
public Object dealCacheService(ProceedingJoinPoint point) throws Throwable {
try {
Method method = getMethod(point);
// 获取注解对象
UseCache useCache = method.getAnnotation(UseCache.class);
//所有参数
Object[] args = point.getArgs();
String fieldKey = parseKey(method, args);
if (StrUtil.isEmpty(fieldKey)) {
return point.proceed();
}
// 这里使用全部拼接是为了打印日志用, 方便查找问题, 如果觉得key太长可以MD5一下
String cacheKey = useCache.totalKeyPreFix() + useCache.keyPrefix() + fieldKey;
UseCache.CacheOperation cacheOperation = useCache.cacheOperation();
if (cacheOperation == UseCache.CacheOperation.DQL) {
return processDQL(point, useCache, cacheKey);
}
if (cacheOperation == UseCache.CacheOperation.DML) {
return processDML(point, cacheKey);
}
} catch (Exception e) {
log.error("dealCacheService error,JoinPoint:{}", point.getSignature(), e);
System.err.println(e);
}
return point.proceed();
}
/**
* 查询处理
*/
private Object processDQL(ProceedingJoinPoint point, UseCache useCache, String cacheKey)
throws Throwable {
if (redisTemplate.hasKey(cacheKey)) {
Object o = redisTemplate.opsForValue().get(cacheKey);
if (useCache.printLog()) {
log.info("{} enable cache service,has cacheKey:{} , return {}", point.getSignature(), cacheKey, o);
}
// 防止并发情况下, 取值时正好过期
if (null != o) {
return o;
}
}
// 存值
Object result = null;
try {
return result = point.proceed();
} finally {
redisTemplate.opsForValue().set(cacheKey, result, useCache.expireTime(), useCache.timeUnit());
log.info("after {} proceed,save result to cache,redisKey:{},save content:{}", point.getSignature(), cacheKey, result);
}
}
/**
* 删除和修改处理
*/
private Object processDML(ProceedingJoinPoint point, String cacheKey)
throws Throwable {
try {
return point.proceed();
} finally {
// 删除掉原来在缓存中的数据,下次查询时就会刷新
redisTemplate.delete(cacheKey);
}
}
private Method getMethod(JoinPoint joinPoint) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
return method;
}
/**
* 获取redis的key
*/
private String parseKey(Method method, Object[] args) {
//获取被拦截方法参数名列表(使用Spring支持类库)
LocalVariableTableParameterNameDiscoverer u =
new LocalVariableTableParameterNameDiscoverer();
String[] paraNameArr = u.getParameterNames(method);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < paraNameArr.length; i++) {
sb.append(paraNameArr[i]).append(args[i]);
}
return sb.toString();
}
}
- 标签
package com.yiweikeji.food.configurer.cache;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.TimeUnit;
/**
* @Author: Fcx
* @Date: 2020/4/21 14:28
* @Version 1.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UseCache {
/**
* 总前缀, 分布式项目防止key冲突, 一般使用项目名或项目简称固定写死
* @return
*/
String totalKeyPreFix() default "";
/**
* key前缀
*/
String keyPrefix();
/**
* 过期时间 2分钟
*/
int expireTime() default 60 * 2;
/**
* 是否打印日志
*/
boolean printLog() default true;
TimeUnit timeUnit() default TimeUnit.SECONDS;
CacheOperation cacheOperation() default CacheOperation.DQL;
/**
* 缓存操作类型
*/
enum CacheOperation {
/**
* 读库
*/
DQL,
/**
* 写库
*/
DML,
;
}
}
如何使用
- 使用场景
一般在service层使用, 贴在实现类上, 达到整个方法的缓存目的 - 使用示例:
// 默认只需要填keyPrefix前缀 和cacheOperation缓存类型 加缓存就用DQL, 删缓存就用DML
@UseCache(keyPrefix = "InstanceAwardServiceImpl_1",
cacheOperation = UseCache.CacheOperation.DQL)
@Override
public List<InstanceAward> cacheList(Integer type, Long instanceId) {
return this.list(
new QueryWrapper<InstanceAward>()
.eq("type", type)
.eq("instance_id", instanceId)
.orderByAsc("sort_num")
);
}