注解实现Redis缓存,使用SpringEL表达式

注解类

package com.nsk666.common.annotation;

import java.lang.annotation.*;

/**
 * @author niushuaikui
 * @description 自定义redis注解
 * @date 2023/8/2
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Redis {
    // 名字前缀
    String nameSpace() default "";

    // key
    String key();

    // 过期时间
    int expireTime() default 60 * 1000;
    // 是否是查询操作,如果是操作数据库设置为false

    boolean read()  default true;
}

切面类

package com.nsk666.framework.aspectj;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.nsk666.common.annotation.Redis;
import com.nsk666.common.core.redis.RedisCache;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.DefaultParameterNameDiscoverer;
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 org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;

/**
 * @author niushuaikui
 * @description Redis缓存切面类
 * @date 2023/8/2
 */
@Aspect
@Component
public class RedisCacheAspect {
    private static final Logger log = LoggerFactory.getLogger(RedisCacheAspect.class);
    @Resource
    RedisCache redisCache;

    @Pointcut(value = "@annotation(com.nsk666.common.annotation.Redis)")
    public void redisCache() {
    }

    @Around("redisCache()")
    private Object saveCache(ProceedingJoinPoint point) throws Throwable {
        // 获取切入方法对象
        // 获取代理对象的方法,不包含注解
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        // 获取目标对象的方法,包含注解
        Method methodWithAnnotation = point.getTarget().getClass()
                .getDeclaredMethod(point.getSignature().getName(), method.getParameterTypes());
        Object result = null;
        // 获取目标方法的注解对象
        Redis annotation = methodWithAnnotation.getAnnotation(Redis.class);
        // 解析key
        String key = parseKey(methodWithAnnotation, point.getArgs(), annotation);
        boolean read = annotation.read();
        // 非读,获取方法执行结果,存入缓存
        if (!read) {
            result = point.proceed();
            // 缓存过期时间
            int expireTime = annotation.expireTime();
            redisCache.setCacheObject(key, JSONObject.toJSONString(result), expireTime);
            return result;
        }
        // 读,则直接从缓存获取,获取不到从数据库获取。
        String cacheObject = redisCache.getCacheObject(key);
        if (cacheObject == null) {
            // Redis中不存在,放注解往下执行,从方法中获取结果
            result = point.proceed();
            // 方法返回结果不为空
            if (result != null) {
                // 缓存过期时间
                int expireTime = annotation.expireTime();
                redisCache.setCacheObject(key, JSONObject.toJSONString(result), expireTime);
                return result;
            } else {
                log.error(key + ",在缓存中未找到,在数据库中未找到");
                return null;
            }
        } else {
            return deSerialize(methodWithAnnotation, cacheObject);
        }
    }

    /**
     * 反序列化
     *
     * @param m     原方法的对应信息
     * @param cache 缓存字符串
     */
    private Object deSerialize(Method m, String cache) {
        // 原方法的返回数据类型类
        Class<?> returnTypeClass = m.getReturnType();
        Object object = null;
        // 原方法的返回数据类型类
        Type returnType = m.getGenericReturnType();
        // 判断是否是ParameterizedType的实例,即泛型
        if (returnType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            for (Type typeArgument : typeArguments) {
                // 如果是泛型则需要将其中每个单独转换
                Class<?> typeArgClass = (Class<?>) typeArgument;
                object = JSON.parseArray(cache, typeArgClass);
            }
        } else {
            // 不是泛型则直接转换
            object = JSON.parseObject(cache, returnTypeClass);
        }
        return object;
    }

    /**
     * 解析注解中的key,命名空间+key+方法名+参数的MD5加密
     *
     * @param method     使用注解的方法
     * @param argValues  方法参数
     * @param annotation 注解
     */
    private String parseKey(Method method, Object [ ] argValues, Redis annotation) {
        // ================================方式1.通过自定义EL表达式缓存key===========================
        String key = annotation.key();
        // 判断key是否是el表达式
        if (key.startsWith("#")) {
            // 表达式解析器
            ExpressionParser parser = new SpelExpressionParser();
            // 解析表达式
            Expression expression = parser.parseExpression(annotation.key());
            // 构建表达式上下文
            StandardEvaluationContext context = new StandardEvaluationContext();
            // 获取指定方法的方法信息
            DefaultParameterNameDiscoverer defaultParameterNameDiscoverer = new DefaultParameterNameDiscoverer();
            // 获取指定方法的参数名
            String [ ] paramNames = defaultParameterNameDiscoverer.getParameterNames(method);
            // 遍历参数名,把参数赋值给参数名
            if (paramNames != null && paramNames.length > 0) {
                for (int i = 0; i < paramNames.length; i++) {
                    context.setVariable(paramNames [i ], argValues [i ]);
                }
            }
            // 经过表达式解析,在上下文寻找能匹配的值   user.id  => 传入的参数名为user 且对应的对象有id属性
            Object o = expression.getValue(context, Object.class);
            if (null == o) {
                log.error("El表达式" + key + ":未匹配" + Arrays.toString(paramNames));
                return annotation.nameSpace() + ":" + key.replace("#","");
            }
            return annotation.nameSpace() + JSONObject.toJSONString(o);
        } else {
            return annotation.nameSpace() + ":" + key;
        }


        // ===================================方式2.通过加密混淆key缓存======================================
//        StringBuilder prefix = new StringBuilder();
//        prefix.append(annotation.nameSpace()).append(".").append(annotation.key());
//        prefix.append(".").append(method.getName());
//        StringBuilder sb = new StringBuilder();
//        for (Object obj : argValues) {
//            sb.append(obj.toString());
//        }
//        return prefix.append(DigestUtils.md5DigestAsHex(sb.toString().getBytes())).toString();
    }

}

测试

    @RequestMapping("/test")
    @Redis(key = "#a")
    public String test(String a,String b){
        System.out.println(a);
        System.out.println(b);
        return a+b;
    }
    @RequestMapping("/test1")
    @Redis(key = "#wxUser.id")
    public WxUser test1(@RequestBody WxUser wxUser){
        System.out.println(wxUser);
        return wxUser;
    }
  1. 请求第一个方法,第一次控制台有输出Apifox有输出,第二次控制台无输出,Apifox有输出

  2. 请求第二个方法,第一次控制台有输出Apifox有输出,第二次控制台无输出,Apifox有输出