package cn.bynav.mower.app.annotation;
import java.lang.annotation.*;
/**
* 添加redis缓存注解
*
* @author JX.LUO
* @date 2023/9/4 14:02
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCacheSave {
/**
* redis缓存中的key
*/
String key() default "";
/**
* 过期时间,默认一天过期
*/
long expire() default 86400L;
/**
* 如果注解添加在返回list的方法上,则需要通过该字段指定list中的class类型
*/
Class<?> type() default Object.class;
}
package cn.bynav.mower.app.annotation;
import java.lang.annotation.*;
/**
* 删除redis缓存注解
*
* @author JX.LUO
* @date 2023/9/4 14:07
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCacheRemove {
String key() default "";
}
package cn.bynav.mower.app.aspect;
import cn.bynav.mower.app.annotation.RedisCacheRemove;
import cn.bynav.mower.app.annotation.RedisCacheSave;
import cn.bynav.mower.app.exception.CommonException;
import cn.bynav.mower.app.util.RedisCache;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static cn.bynav.mower.app.enums.AppHttpCodeEnum.API_TIMEOUT;
/**
* redis缓存切面
*
* @author JX.LUO
* @date 2023/9/4 14:10
*/
@Aspect
@Component
@Slf4j
public class RedisCacheAspect {
// 项目名
private static final String APP_NAME = "mower:admin";
// redis分隔符
private static final String KEY_SEPARATOR = ":";
// redisson lock
private static final String REDISSON_LOCK = ":lock";
// spEl表达式转换器
private final ExpressionParser parser
= new SpelExpressionParser();
// 方法参数名搜索器
private final LocalVariableTableParameterNameDiscoverer discoverer
= new LocalVariableTableParameterNameDiscoverer();
@Autowired
private RedisCache redisCache;
@Autowired
private RedissonClient redissonClient;
/**
* 在使用RedisCacheSave注解的地方切入此切点,查询缓存是否在redis中存在,若已存在,则直接返回,否则查询数据库
*
* @param joinPoint 切入点信息
* @return java.lang.Object 方法返回值
*/
@Around("@annotation(cn.bynav.mower.app.annotation.RedisCacheSave)")
private Object handleCache(final ProceedingJoinPoint joinPoint) throws Throwable {
// 获取切入的方法对象
// 这个method是代理对象的,没有包含注解
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
// this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解
Method methodWithAnnotations = joinPoint
.getTarget()
.getClass()
.getDeclaredMethod(joinPoint.getSignature().getName(), method.getParameterTypes());
// 根据目标方法对象获取注解对象
RedisCacheSave cacheAnnotation = methodWithAnnotations.getDeclaredAnnotation(RedisCacheSave.class);
// 解析key
String keyExpr = cacheAnnotation.key();
Object[] args = joinPoint.getArgs();
String key = getRedisKeyBySpEl(keyExpr, methodWithAnnotations, args);
// 到redis中获取缓存
String cache = null;
try {
cache = redisCache.getCacheObject(key);
} catch (Exception e) {
log.error("key:{},查询redis缓存异常:{}", keyExpr, e.getMessage());
}
/*
缓存未命中
*/
if (StrUtil.isBlank(cache)) {
// 使用分布式锁防止缓存击穿
RLock lock = redissonClient.getLock(key + REDISSON_LOCK);
// 尝试加锁,等待时间3s,不设置释放时间,redisson自动看门狗机制续期
boolean isLocked = lock.tryLock(3, TimeUnit.SECONDS);
// 成功加锁
if (isLocked) {
// 再次尝试能否获取到缓存
cache = redisCache.getCacheObject(key);
if (StrUtil.isBlank(cache)) {
// 若获取不到缓存,说明缓存更新失败,到数据库中去获取,并更新缓存
Object result = null;
try {
result = joinPoint.proceed();
// 从数据库获取后存入redis,若有指定过期时间,则设置
long expireTime = cacheAnnotation.expire();
// 此时result如果为空也将空值存入redis,防止缓存穿透
if (expireTime > 0) {
redisCache.setCacheObject(key, JSON.toJSONString(result), expireTime, TimeUnit.SECONDS);
} else {
redisCache.setCacheObject(key, JSON.toJSONString(result));
}
} catch (Exception e) {
log.warn("{} = {},缓存redis异常:{}", keyExpr, e.getMessage(), result);
} finally {
// 解锁
lock.unlock();
}
return result;
} else {
// 获取到缓存,说明缓存已被更新成功,直接解锁,进入缓存命中流程
lock.unlock();
}
}
// 加锁失败 => 返回超时
else {
throw new CommonException(API_TIMEOUT);
}
}
/*
缓存命中
*/
// 得到被代理的方法上的注解
Class<?> modelType = cacheAnnotation.type();
// 得到被代理方法的返回值类型
Class<?> returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
// 返回反序列化从缓存中拿到的json
return deserialize(cache, returnType, modelType);
}
@Around("@annotation(cn.bynav.mower.app.annotation.RedisCacheRemove)")
private Object handleCacheEvict(ProceedingJoinPoint joinPoint) throws Throwable {
// 获取切入的方法对象
// 这个m是代理对象的,没有包含注解
Method m = ((MethodSignature) joinPoint.getSignature()).getMethod();
// this()返回代理对象,target()返回目标对象,目标对象反射获取的method对象才包含注解
Method methodWithAnnotations = joinPoint
.getTarget()
.getClass()
.getDeclaredMethod(joinPoint.getSignature().getName(), m.getParameterTypes());
// 根据目标方法对象获取注解对象
RedisCacheRemove cacheEvictAnnotation = methodWithAnnotations.getDeclaredAnnotation(RedisCacheRemove.class);
// 解析key
String keyExpr = cacheEvictAnnotation.key();
Object[] args = joinPoint.getArgs();
String key = getRedisKeyBySpEl(keyExpr, methodWithAnnotations, args);
// 先操作数据库再删除缓存
Object result = joinPoint.proceed();
redisCache.deleteObject(key);
return result;
}
/**
* 解析注解中的key, 支持spEl表达式的解析
*
* @param spElExpress 注解中spEl表达式
* @param method 方法对象
* @param params 方法参数
* @return java.lang.String
*/
private String getRedisKeyBySpEl(String spElExpress, Method method, Object[] params) {
String redisKey = APP_NAME + KEY_SEPARATOR + method.getName();
// 如果为空,则默认服务名:方法名
if (StrUtil.isBlank(spElExpress)) {
return redisKey;
}
// 如果不是spEl表达式,则直接使用用户传入的key
if (!spElExpress.contains("#")) {
return spElExpress;
}
// 如果是spEl表达式,但是参数为空,则默认服务名:方法名
if (params == null) {
return redisKey;
}
// 获取方法的参数名称
String[] parameterNames = discoverer.getParameterNames(method);
EvaluationContext context = new StandardEvaluationContext();
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], params[i]);
}
}
// 解析spEl表达式
try {
Expression expression = parser.parseExpression(spElExpress);
Object value = expression.getValue(context);
// 解析成功redis键 => 项目名:填入表达式
return APP_NAME + KEY_SEPARATOR + Objects.toString(value, "");
} catch (Exception e) {
// 解析错误使用默认redis键 => 服务名:方法名
return redisKey;
}
}
/**
* FastJSON反序列化获得对象
*
* @param json 从redis缓存中获取的字符串
* @param clazz 添加注解的方法返回值的class类型
* @param modelType 转换成list中的class类型
* @return java.lang.Object
*/
private Object deserialize(String json, Class<?> clazz, Class<?> modelType) {
return clazz.isAssignableFrom(List.class) ? JSON.parseArray(json, modelType) : JSON.parseObject(json, clazz);
}
}