• redis缓存切面实现(支持缓存key的spel表达式)


    1.定义注解

    package com.g2.order.server.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * redis缓存注解
     * 仅支持方法
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface RedisCachetAttribute {
        /**
         * @return 缓存的key值
         * 对应的Method的返回值必须 实现 Serializable 接口
         *
         */
        String key();
    
        /**
         * 到期秒数
         *
         * @return 到期秒数
         */
        int expireSeconds() default 20;
    }

    2.定义切面

    package com.g2.order.server.aspect;
    
    import com.g2.order.server.annotation.RedisCachetAttribute;
    import com.g2.order.server.utils.ObjectUtils;
    
    import org.aspectj.lang.JoinPoint;
    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.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.core.DefaultParameterNameDiscoverer;
    import org.springframework.core.ParameterNameDiscoverer;
    import org.springframework.core.annotation.Order;
    import org.springframework.expression.EvaluationContext;
    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 redis.clients.jedis.JedisCluster;
    
    //开启AspectJ 自动代理模式,如果不填proxyTargetClass=true,默认为false,
    @EnableAspectJAutoProxy(proxyTargetClass = true)
    @Component
    @Order(-1)
    @Aspect
    public class RedisCacheAspect {
        /**
         * 日志
         */
        private static Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);
    
        /**
         * SPEL表达式解析器
         */
        private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();
    
        /**
         * 获取方法参数名称发现器
         */
        private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
    
        /**
         * Redis集群
         */
        @Autowired
        private JedisCluster jedisCluster;
    
        /**
         * 切面切入点
         */
        @Pointcut("@annotation(com.g2.order.server.annotation.RedisCachetAttribute)")
        public void mergeDuplicationRequest() {
    
        }
    
        /**
         * 环绕切面
         */
        @Around("mergeDuplicationRequest()")
        public Object handleControllerMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            //获取controller对应的方法.
            MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
            //获取方法
            Method method = methodSignature.getMethod();
    
            //获取注解
            RedisCachetAttribute annotation = method.getAnnotation(RedisCachetAttribute.class);
            //获取缓存key的表达式,并根据上下文等数据计算表达式
            String cacheKey = parseKey(annotation.key(), proceedingJoinPoint);
            int seconds = annotation.expireSeconds();
            //先尝试从redis里获取数据(字节)
            byte[] redisKey = cacheKey.getBytes();
            byte[] redisValue = jedisCluster.get(redisKey);
    
            if (redisValue != null && redisValue.length > 0) {
                //redis有数据,直接返回
                return ObjectUtils.toObject(redisValue);
            }
    
            //redis没有数据,则调用原方法,获取结果值
            Object result = proceedingJoinPoint.proceed();
            //将返回值序列化为Byte[],保存到redis
            redisValue = ObjectUtils.toByteArray(result);
            jedisCluster.setex(redisKey, seconds, redisValue);
    
            return result;
        }
    
        /**
         * 计算spel表达式
         *
         * @param expression 表达式
         * @param context    上下文
         * @return String的缓存key
         */
        private static String parseKey(String expression, JoinPoint context) {
            //获取切入点的方法信息
            MethodSignature methodSignature = (MethodSignature) context.getSignature();
            Method method = methodSignature.getMethod();
    
            // 获取传入参数值
            Object[] args = context.getArgs();
            if (args == null || args.length == 0) {
                // 无参传入,直接计算表达式(无需参数上下文)
                return EXPRESSION_PARSER.parseExpression(expression).getValue(String.class);
            }
    
            // 获取参数名
            String[] parameterNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);
            if (parameterNames.length > args.length) {
                //由于java不允许有匿名参数,所以如果参数名多于参数值,则必为非法
                logger.error("参数值的长度少于参数名长度, 方法:{}, 参数名长度: {},参数值长度:{}", method, parameterNames.length, args.length);
                throw new IllegalArgumentException("参数传入不足");
            }
    
            // 将参数名与参数值放入参数上下文
            EvaluationContext evaluationContext = new StandardEvaluationContext();
            for (int i = 0; i < parameterNames.length; i++) {
                evaluationContext.setVariable(parameterNames[i], args[i]);
            }
    
            // 计算表达式(根据参数上下文)
            return EXPRESSION_PARSER.parseExpression(expression).getValue(evaluationContext, String.class);
        }
    }

    3.引用代码

    package com.g2.order.server.business;
    
    import com.google.common.collect.ImmutableMap;
    import com.g2.order.server.annotation.RedisCachetAttribute;
    import com.g2.order.server.business.vo.JsonResult;
    import org.springframework.stereotype.Component;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    
    /**
     * SettingBusiness.
     */
    @Component
    public class SettingBusiness {
    
        @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" +
                ".SETTING_JSON_KEY" +
                " + #code"
                , expireSeconds = 30
        )
        public JsonResult getSetting(String code) {
            return new JsonResult("234");
        }
    
        @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" +
                ".SETTING_LIST_KEY" +
                " + #code"
                , expireSeconds = 30
        )
        public List<JsonResult> getSettingList(String code) {
            return Arrays.<JsonResult>asList(new JsonResult("234"), new JsonResult("345"));
        }
    
        @RedisCachetAttribute(key = "T(com.g2.order.server.vo.CommonConst)" +
                ".SETTING_MAP_KEY" +
                " + #code"
                , expireSeconds = 30
        )
        public Map<String, JsonResult> getSettingMap(String code) {
            return ImmutableMap.of(code, new JsonResult("234"),"abc",new JsonResult("abc234"));
        }
    }
    package com.g2.order.server.business.vo;
    
    import java.io.Serializable;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    /**
     * Model
     */
    
    @AllArgsConstructor
    @NoArgsConstructor
    @Data
    public class JsonResult implements Serializable {
        private Object data;
    }

    4.测试如下.(验证获取普通POJO,List,Map的返回结构)

    package com.g2.order.server.controller;
    
    import com.g2.order.server.business.SettingBusiness;
    import com.g2.order.server.business.vo.JsonResult;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.ResponseBody;
    import org.springframework.web.bind.annotation.RestController;
    import java.util.List;
    import java.util.Map;
    import io.swagger.annotations.Api;
    
    
    @Api(value = "H5Controller", description = "H5接口")
    @RestController
    @RequestMapping("/h5")
    public class H5Controller {
        private static Logger logger = LoggerFactory.getLogger(H5Controller.class);
    
        @Autowired
        private SettingBusiness settingBusiness;
    
        @ResponseBody
        //@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"})
        @RequestMapping(value = "/{code}.jsonp", method = RequestMethod.GET)
        public Object testJsonp1(@PathVariable("code") String code) {
            // return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234"));
            JsonResult result = settingBusiness.getSetting(code);
            logger.info("获取结果,参数:{},值:{}", code, result);
            return result;
        }
    
        @ResponseBody
        //@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"})
        @RequestMapping(value = "/{code}.LIST", method = RequestMethod.GET)
        public Object testJsonp2(@PathVariable("code") String code) {
            // return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234"));
            List<JsonResult> result = settingBusiness.getSettingList(code);
            logger.info("获取结果,参数:{},值:{}", code, result);
            return result;
        }
    
        @ResponseBody
        //@MergeDuplicationRequestAttribute(redisLockKeyTemplate = "A:%s", redisLockKeyObjectFileds = {"code"})
        @RequestMapping(value = "/{code}.MAP", method = RequestMethod.GET)
        public Object testJsonp3(@PathVariable("code") String code) {
            // return JsonMapper.INSTANCE.toJsonP("callabc111", new JsonResult("234"));
            Map<String,JsonResult> result = settingBusiness.getSettingMap(code);
            logger.info("获取结果,参数:{},值:{}", code, result);
            return result;
        }
    }

    5.辅助代码

    package com.g2.order.server.utils;
    
    import com.google.common.collect.Lists;
    
    import java.beans.IntrospectionException;
    import java.beans.PropertyDescriptor;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    import java.lang.reflect.Array;
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.util.List;
    import java.util.Map;
    import java.util.function.Function;
    import java.util.stream.Collectors;
    
    /**
     * Object帮助类
     */
    public class ObjectUtils {
    
       
    
    /**
     * 对象转Byte数组
     */
        public static byte[] toByteArray(Object obj) throws IOException {
            byte[] bytes = null;
            try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
                 ObjectOutputStream oos = new ObjectOutputStream(bos)) {
                oos.writeObject(obj);
                oos.flush();
                bytes = bos.toByteArray();
            }
            return bytes;
        }
    
        /**
         * Byte数组转对象
         */
        public static Object toObject(byte[] bytes) throws IOException, ClassNotFoundException {
            Object obj = null;
            try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
                 ObjectInputStream ois = new ObjectInputStream(bis)) {
    
                obj = ois.readObject();
            }
            return obj;
        }
    }
    package com.g2.order.server.vo;
    
    /**
     * CommonConst 
     */
    public class CommonConst {
    
        public static final String PREFIX = "g2:";
        public static final String SETTING_PREFIX = PREFIX + "setting:";
        /**
         * JSON_KEY
         */
        public static final String SETTING_JSON_KEY = SETTING_PREFIX + "json:";
    
        /**
         * LIST_KEY
         */
        public static final String SETTING_LIST_KEY = SETTING_PREFIX + "list:";
    
        /**
         * MAP_KEY
         */
        public static final String SETTING_MAP_KEY = SETTING_PREFIX + "map:";
    }

    6.备注

    这只是一个实现上的demo,如果要用到生产,可能还需要做以下改进

    1.切面代码里写死了JedisCluster,这里要修改成一个接口 来支持单机/哨兵/集群 等

    2.不支持毫秒级的存储(因为jedisCluster不支持...)

    3.当没有获取缓存值时,应当根据key来加分布式锁,否则容易造成同样的处理多次执行

  • 相关阅读:
    python3+selenium框架设计04-封装测试基类
    python3+selenium框架设计02-自动化测试框架需要什么
    python3+selenium框架设计01-Page Object
    python3+selenium入门16-窗口截图
    python3+selenium入门15-执行JavaScript
    爬虫入门
    NLP整体流程的代码
    NLTK与NLP原理及基础
    NLTK词性标注解释
    [hdu4355]Party All the Time(三分)
  • 原文地址:https://www.cnblogs.com/zhshlimi/p/11125740.html
Copyright © 2020-2023  润新知