Spring AOP加注解实现redis缓存

今天来做一个通过注解的形式实现redis缓存

在我们平时的redis的使用中经常会有一种写法,就是先查redis,如果redis中没有,再查数据库。

 String key = KEY + "showButton";
        if (redisClient.hasKey(key)) {
            String str = redisClient.get(key, String.class);
            result = JSON.parseArray(str, VaButtonConfig.class);
            log.info("从redis获取按钮配置");
            return result;
        }else {
            result = buttonConfigMapper.selectByExample(null);
            redisClient.set(key,result,7,TimeUnit.DAYS);
            log.info("将按钮配置存入redis");
            return result;
        }

像这种套路性的代码太多可以说有很多“重复”代码了。这里我们来做一个注解来实现上面的功能,简化我们的代码。

新建一个注解RedisCache,key表示我们redis缓存的key。expire缓存时间
package com.lw.study.redisInterupter;

import java.lang.annotation.*;

/**
 * @author 
 * @date 10:28 2019/6/26
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Documented
public @interface RedisCache {
    String key();
    long expire() default -1;
}

RedisCache上面的三个注解是元注解(用来描述注解的一种方式)

  • @Retention //定义注解的生命周期(source-class-runtime)
  • @Target //描述注解的应用范围
  • @Documented //文档注解 会被javadoc工具文档化
在我们使用redis缓存的时候key值通常会使用字符串(表示业务模块)+特殊符号(冒号,下划线)+特定的参数值来拼接,所以这里我们会用到spring EL表达式。具体使用大家可以查看官方文档,我这里举个例子:用#id来获取参数中id的值,用#name来获取参数中name 的值。然后拼在一起。要注意的是,我们的常量字符串需要用单引号括起来。拼接的字符串之间用加号连接
    @Override
    @RedisCache(key = "'study:' + #id +':' + #name")
    public User selectUserById(int id,String name) {
        return userMapper.selectByPrimaryKey(id);
    }

重点来了,新建一个切面RedisCacheAspect,通过aop在使用到我们注解的地方把数据存入缓存

@Component
@Aspect
public class RedisCacheAspect {
    public static final Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);
    //这里使用StringRedisTemplate来操作redis
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Around("@annotation(com.lw.study.redisInterupter.RedisCache)")
    public Object cacheInterceptor(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        //获取到目标方法
        Method method = pjp.getTarget().getClass().getMethod(signature.getName(), signature.getParameterTypes());
        //获取方法注解
        RedisCache redisCache = method.getAnnotation(RedisCache.class);
        String keyEl = redisCache.key();
        //创建解析器 解析EL表达式
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(keyEl);
        //设置解析上下文(这些占位符的值 来自)
        EvaluationContext context = new StandardEvaluationContext();
        //参数值 获取到参数实际的值
        Object[] args = pjp.getArgs();
        //我们还需要获取实际的参数名,而不是agrs0,agrs1这种形式,通过下面的方式可以获取
         // public DefaultParameterNameDiscoverer() {
        //        if (standardReflectionAvailable) {
        //            this.addDiscoverer(new StandardReflectionParameterNameDiscoverer());
        //        }
        //
        //        this.addDiscoverer(new LocalVariableTableParameterNameDiscoverer());
        //    }
        //他会调用LocalVariableTableParameterNameDiscoverer去实现
        DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        for(int i = 0; i < parameterNames.length; i++) {
            //name-value : userId-10010
            context.setVariable(parameterNames[i],args[i]);
        }
        //解析出key的真实值
        String key = expression.getValue(context).toString();
        System.out.println(key);
        String object = stringRedisTemplate.opsForValue().get(key);
        if (object == null) {
            //获取方法执行结果
            Object data = pjp.proceed();
            System.out.println("从数据库获取结果");
            //缓存时间
            long expireTime = redisCache.expire();
            if (expireTime == -1) {
                stringRedisTemplate.opsForValue().set(key,JSON.toJSONString(data));
            }else {
                stringRedisTemplate.opsForValue().set(key, JSON.toJSONString(data),expireTime, TimeUnit.SECONDS);
            }
            return data;
        }else {
            System.out.println("从redis获取");
            //这里object套一层JSON.parse()是因为有时候存入redis的json字符串get出来后会多一个“\”转义符号导致直接parse失败
            return JSON.parseObject(JSON.parse(object).toString(),signature.getReturnType());
        }

    }
}

好了,写一个测试类测试一下

 @Test
    public void testCache() {
        User user = userService.selectUserById(1002, "橘子");
        System.out.println(user.toString());

    }
debug一下,我们的keyEl被解析出来了,然后用这个key去做缓存。先从缓存取数,没有则从数据库取,我们还可以根据业务加上一些特定的逻辑,比如如果redis和数据库都没有数据,为了防止缓存击穿我们可以做参数校验、往redis存入特定的值并设置有效时间(防止无效数据不停的查询数据库)。

Spring AOP加注解实现redis缓存_第1张图片
在这里插入图片描述
在这里插入图片描述

需要注意的是这个注解我们设置了有效范围是在方法上,所以当我们的一个方法业务逻辑很多,缓存只是其中的一个环节的时候,我们可以把缓存的环节抽出来做为一个方法。

你可能感兴趣的:(学习笔记,问题解析)