redis自定义注解来实现缓存

原理是使用aop

1. 创建注解

package com.zykj.newsell.common.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * redis缓存注解
 *
 * @author lc
 * @version 1.0
 * @date 2022/4/19 14:41
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)     //运行时存在,SOURCE则编译时不存在,如@overwrited
public @interface RedisCacheAnnotation {
    //注解被自定义的RedisCacheProxy使用,则拥有其功能
    String redisKeyPrefix() default "newsell:";// redisKey的默认前缀

    String value();
}

2.切面类

可以使用多个注解,但是切面类是只有一个的,可以有多个环绕通知

package com.zykj.newsell.common.aop;

import com.zykj.newsell.common.enums.ResultCodeEnum;
import com.zykj.newsell.common.exception.ServiceException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 参数判断
 * 用户米和密码格式判断
 *
 * @author lc
 * @version 1.0
 * @date 2022/4/18 13:21
 */
@Aspect
@Component
@Slf4j
public class ParameterJudgeAop {

    @Autowired
    private RedisTemplate redisTemplate;

    @Around("@annotation(com.zykj.newsell.common.aop.ParameterJudgeAnnotation)")
    public Object aroundAdviceByParam(ProceedingJoinPoint pjp) throws Throwable {
        String reg = "^[a-zA-Z0-9]+$"; // 只允许中文数字英文
        // 获取方法签名
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Object[] args = pjp.getArgs();
        // 对参数进行处理
        args = Arrays.stream(args).map(arg -> {
            Optional argOptional = Optional.ofNullable(arg);
            if (!argOptional.isPresent() || !arg.toString().matches(reg)) {
                throw new ServiceException(ResultCodeEnum.BAD_REQUEST_PARAM);
            }
            return arg;
        }).toArray(Object[]::new);
        // 放行
        return pjp.proceed(args);
    }




    @Around("@annotation(com.zykj.newsell.common.aop.RedisCacheAnnotation)")
    public Object aroundAdviceByRedisCache(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature(); //获取信息
        Method method = signature.getMethod();
        RedisCacheAnnotation annotation = method.getAnnotation(RedisCacheAnnotation.class);         //获取方法上的注解
        Class returnType = method.getReturnType();
        Object[] args = joinPoint.getArgs();
        String redisKey=annotation.redisKeyPrefix();
        String value = annotation.value();
        redisKey=redisKey+value;//从自定义注解获取key前缀

        Object cache = redisTemplate.opsForValue().get(redisKey);
        // 1.没有缓存
        if (null==cache){
            // 1.1 开启redis_nx分布式锁
            String strUUID = UUID.randomUUID().toString(); // 随机设置值
            // true 表示加锁,false表示被锁
            Boolean isOK = redisTemplate.opsForValue().setIfAbsent(redisKey+":lock", strUUID, 2, TimeUnit.SECONDS);
            if (isOK){
                try {
                    // 1.2 执行被代理方法,访问db
                    cache = joinPoint.proceed();
                    // 1.3 并更新缓存
                    if (null!=cache){
                        redisTemplate.opsForValue().set(redisKey,cache);
                    }else {
                        // 1.4 防止不存在的key高频访问,(给缓存假数据instance)
                        redisTemplate.opsForValue().set(redisKey,returnType.newInstance(),20,TimeUnit.SECONDS);
                    }
                } catch (Throwable throwable) {
                    throwable.printStackTrace();
                }

                // 释放锁
                // lua脚本释放锁
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                // 设置lua脚本返回的数据类型
                DefaultRedisScript redisScript = new DefaultRedisScript<>();
                // 设置lua脚本返回类型为Long
                redisScript.setResultType(Long.class);
                redisScript.setScriptText(script);
                redisTemplate.execute(redisScript, Arrays.asList(redisKey + ":lock"), strUUID);
            }else {
                // 没有拿到锁的请求,自旋,回调重新获取缓存,不能直接返回null
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return redisTemplate.opsForValue().get(redisKey);
            }
        }
//执行被代理方法,访问db
//        try {
//            cache = joinPoint.proceed();
//        } catch (Throwable throwable) {
//            throwable.printStackTrace();
//        }
        // 2.有缓存直接放行缓存
        System.out.println("aop代理redis缓存成功");
        //虽然返回的是Object类型,但是spirngAOP会帮我们转为代理方法需要的类型,如Map
        //return cache;
        return joinPoint.proceed();
    }
}

 
  

3.使用


    /**
     * @return 用户信息
     */
    @GetMapping("/getUserInfo")
    @ApiOperation(value = "获取用户信息,token登录之后要执行的接口")
    @RedisCacheAnnotation("2")
    public ResultInfo getUserInfo() {
        return ResultInfo.ok(userService.getUserInfo());
    }

第一次没有数据,缓存进redis,后面你的请求就会直接走缓存了
redis自定义注解来实现缓存_第1张图片

注意,假如更改了缓存的数据的原数据源数据,那就清除这个缓存。

你可能感兴趣的:(redis,java)