从永远到永远-自定义Redis缓存注解

1.需求

最近在做一个APP首页多个报表,报表查询逻辑很复杂但数据并非实时的,同一个商家的多个账号进入首页都会加载报表数据。这样,就导致首页加载速度缓慢,数据库查询压力增大。为此引入Redis缓存,因接口较多(七八个),每个接口中写一遍相似的缓存处理,太重复,对代码入侵严重。在此,采取自定义Redis缓存注解结合AOP实现。

2.方案

1.自定义注解

@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface EpoRedisCache {

    /**
     * 描述
     *
     * @return 描述
     */
    String desc() default "";

    /**
     * 前缀
     *
     * @return 前缀
     */
    String prefix() default "";

    /**
     * 后缀
     *
     * @return 后缀
     */
    String suffix() default "";

    /**
     * 过期时间
     *
     * @return 过期时间
     */
    long expire() default 60;

}

2.AOP

@Aspect
@Component
@Slf4j
public class EpoRedisCacheAspect {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private ExpressionParser parser = new SpelExpressionParser();

    private LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();

    @Around("@annotation(redisCache)")
    public Object around(ProceedingJoinPoint pjp, EpoRedisCache redisCache) throws Throwable {
        String k = redisCache.prefix();
        long expire = redisCache.expire();
        Method method = obtainMethod(pjp);
        Type genericReturnType = method.getGenericReturnType();
        if (StringUtils.isNotBlank(redisCache.suffix())) {
            k = StrUtil.join(StrUtil.COLON, k, parseSpel(method, pjp.getArgs(), redisCache.suffix()));
        }
        String v = redisTemplate.opsForValue().get(k);
        Object result;
        if (StringUtils.isNotBlank(v)) {
            result = JSONObject.parseObject(v, genericReturnType);
        } else {
            result = pjp.proceed();
            redisTemplate.opsForValue().set(k, JSONObject.toJSONString(result), expire, TimeUnit.SECONDS);
        }
        return result;
    }

    private Object parseSpel(Method method, Object[] args, String spel) {
        String[] parameterNames = discoverer.getParameterNames(method);
        EvaluationContext context = new StandardEvaluationContext();
        if (null != parameterNames) {
            for (int i = 0; i < parameterNames.length; i++) {
                context.setVariable(parameterNames[i], args[i]);
            }
            try {
                Expression expression = parser.parseExpression(spel);
                return expression.getValue(context);
            } catch (Exception e) {
                return StrUtil.EMPTY;
            }
        }
        return null;
    }


    /**
     * 根据ProceedingJoinPoint切面作用的方法
     *
     * @param pjp proceedingJoinPoint
     * @return method
     */
    private Method obtainMethod(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = pjp.getTarget()
                        .getClass()
                        .getDeclaredMethod(pjp.getSignature().getName(), pjp.getSignature().getDeclaringType());
            } catch (NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
        return method;
    }
}

3.注解使用

@RequestMapping("/spel")
@RestController
public class SpelSampleController {

    @RequestMapping(value = "/test", method = RequestMethod.GET)
    @EpoRedisCache(prefix = "aaa", suffix = "#arg+'_'+#param", expire = 10, desc = "测试")
    public R<SampleEntity> test(String arg, String param) {
        long start = System.currentTimeMillis();
        try {
            //模拟查询数据库
            Thread.sleep(10_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        SampleEntity sampleEntity = new SampleEntity("小明", 12);
        System.out.println("花费时间:"+(System.currentTimeMillis()-start));
        return R.data(sampleEntity);
    }
}

3.问题点

系统有一千个商家,每个商家的几十个账号看到的是同一个商家的数据。也就是说,Redis的key应该能够根据不同商家有所区别。使用了注解实现缓存功能,就无法像代码中那样,直接使用参数拼接到Redis的key中。我希望能够根据参数中的查询条件,动态设置Redis的Key。查了很多资料,决定选择SPEL表达式实现。

1.SPEL表达式和EvaluationContext

后缀填的是SPEL表达式字符串,用于动态获取参数列表参数,与前缀拼接共同组成Redis缓存的key。
官方文档: 官方文档
parseExpression(“表达式”) 会获取一个Expression执行类。
getValue():获取表达式计算结果结果
getValue() returen Object :是在 默认上下文ApplicationContext中的计算结果。
getValue(EvaluationContext.class):相当于自定义上下文,限定了计算范围。
EvaluationContext 创建其子类 new StandardEvaluationContext() 创建自定义容器
evaluationContext.setVariable(key,value);将数据限制到自定义的上下文中。

2.LocalVariableTableParameterNameDiscoverer

LocalVariableTableParameterNameDiscoverer.getParameterNames(Method实例):用于获取该方法参数列表的名称数组。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值