模拟springcache 注解实现 缓存操作

模拟springcache 注解实现 缓存操作

背景就是 每次都需要查询判断 不为空 返回 缓存里面数据,其实有点重复代码

提供两种方式
  1. 直接使用springcache

  2. 自定义注解 + aop 实现

    2.1 定义两个注解

    2.2 实现切面 获取注解里面的值进行对应的逻辑判断

    Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    @Documented
    public @interface CacheKey {
    
        /**
         * 缓存键名称注解,默认值为类名称
         *
         * @return
         */
        String key() default "cacheKey:";
    
        /**
         * 缓存过期时间,默认3分钟
         *
         * @return
         */
        int expireTime() default 3*60;
    
        /**
         * 缓存过期时间单位,默认为分钟
         *
         * @return
         */
        TimeUnit timeUnit() default TimeUnit.SECONDS;
    
        /**
         * 缓存事件 默认查询
         * @return
         */
        CacheEvent cacheEvent() default CacheEvent.QUERY;
    
    }
    
    /**
     * 参数注解,用于指定要获取的参数字段。
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.PARAMETER})
    @Documented
    public @interface ParameterCacheKey {
        /**
         * 参数字段
         *
         * @return
         */
        String fieldName() default "";
    }
    
    Aspect
    @Service
    @Slf4j
    public class QueryCacheAspect {
    
        @Autowired
        private RedisTemplate redisTemplate;
    
    
        /**
         * 定义拦截规则:拦截所有@QueryCache注解的方法。
         */
        @Pointcut("@annotation(com.cnest.cacheredis.common.annotation.CacheKey)")
        public void queryCachePointcut() {
        }
    
        /**
         * 拦截器具体实现
         *
         * @param joinPoint
         * @return
         * @throws Throwable
         */
        @Around("queryCachePointcut()")
        public Object interceptor(ProceedingJoinPoint joinPoint) throws Throwable {
            long beginTime = System.currentTimeMillis();
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            //获取被拦截的方法
            Method method = signature.getMethod();
            //获取方法注解
            CacheKey cacheKey = method.getAnnotation(CacheKey.class);
    
            CacheEvent cacheEvent = cacheKey.cacheEvent();
            String key = cacheKey.key();
    
            StringBuilder realKey = new StringBuilder(cacheKey.key());
    
            // 循环所有的参数
            Object[] args = joinPoint.getArgs();
            for (int i = 0; i < args.length; i++) {
                MethodParameter methodParam = new SynthesizingMethodParameter(method, i);
                //获取参数注解
                Annotation[] parameterAnnotations = methodParam.getParameterAnnotations();
                // 循环参数上所有的注解
                for (Annotation paramAnn : parameterAnnotations) {
                    if (paramAnn instanceof ParameterCacheKey) {
                        ParameterCacheKey parameterCacheKey = (ParameterCacheKey) paramAnn;
                        //取到ParameterCacheKey的标识参数的值
                        Object fieldValue = AopMethodUtil.getParamValue(args[i], parameterCacheKey.fieldName());;
                        realKey.append(":").append(parameterCacheKey.fieldName()).append(":").append(fieldValue);
                        break;
                    }
                }
            }
    
            //获取不到key值,抛异常
            if (StringUtils.isBlank(realKey.toString())) throw new RuntimeException("****缓存key值不存在****");
            // 如果没有 ParameterCacheKey 注解 默认md5(类名+方法+参数)
            if ((Objects.equals(realKey.toString(), key))) {
                realKey.append(":").append(getKey(signature, joinPoint.getArgs()));
            }
            log.debug("获取到缓存key值 {} ", realKey);
            boolean hasKey = redisTemplate.hasKey(realKey.toString());
            if (hasKey) {
                // 根据对应操作
                if (Objects.equals(cacheEvent, CacheEvent.QUERY)) {
                    // 缓存中获取到数据,直接返回。
                    Object object = redisTemplate.opsForValue().get(realKey.toString());
                    log.debug("缓存命中耗时:{}", (System.currentTimeMillis() - beginTime));
                    return object;
                }
                if (Objects.equals(cacheEvent, CacheEvent.DELETE)) {
                    // 删除缓存
                    redisTemplate.delete(realKey.toString());
                    log.debug("缓存删除key {} ", realKey);
                }
            }
    
            Object object = joinPoint.proceed();
            //设置缓存
            if (Objects.equals(cacheEvent, CacheEvent.QUERY)) {
                redisTemplate.opsForValue().set(realKey.toString(), object, cacheKey.expireTime(),
                        cacheKey.timeUnit());
            }
    
            log.debug("结束耗时:{}", (System.currentTimeMillis() - beginTime));
            return object;
        }
    
        /**
         * 默认md5(类名+方法+参数)
         *
         * @param methodSignature
         * @param args
         * @return
         */
        private String getKey(MethodSignature methodSignature, Object[] args) {
            StringBuilder key = new StringBuilder(methodSignature.getDeclaringTypeName());
            key.append(methodSignature.getMethod().getName());
            Object[] paramValues = args;
            String[] paramNames = (methodSignature).getParameterNames();
            for (int i = 0; i < paramNames.length; i++) {
                key.append(paramValues[i]);
            }
            return MD5.create().digestHex(key.toString());
        }
    }
    
    
    @Slf4j
    public class AopMethodUtil {
        /**
         * 基础数据类型
         */
        private static List<String> types =
                Arrays.asList("java.lang.Integer", "java.lang.Double",
                        "java.lang.Float", "java.lang.Long", "java.lang.Short",
                        "java.lang.Byte", "java.lang.Boolean", "java.lang.Char",
                        "java.lang.String","java.math.BigDecimal", "int", "double", "long", "short", "byte",
                        "boolean", "char", "float");
    
        /**
         * 获取参数值
         *
         * @param arg
         * @param fieldName 参数字段名
         * @return
         */
        public static Object getParamValue(Object arg, String fieldName) {
            Object value = "";
            // 获取对象类型
            if (Objects.nonNull(arg)) {
                String typeName = arg.getClass().getTypeName();
                //1 判断是否是基础类型
                if (types.contains(typeName)) {
                    value = arg;
                } else {
                    //2 通过反射获取实体类属性
                    value = getFieldsValue(arg, fieldName);
                }
            }
            return value;
        }
    
        /**
         * 解析实体类,获取实体类中的指定属性値
         *
         * @param object    参数实体对象
         * @param fieldName 字段
         * @return
         */
        public static Object getFieldsValue(Object object, String fieldName) {
            //通过反射获取所有的字段,getFileds()获取public的修饰的字段
            //getDeclaredFields获取private protected public修饰的字段
            Field[] fields = object.getClass().getDeclaredFields();
            String typeName = object.getClass().getTypeName();
            if (types.contains(typeName)) {
                return object;
            }
            Object value = "";
            for (Field field : fields) {
                //在反射时能访问私有变量
                field.setAccessible(true);
                try {
                    //如果实体类里面继续包含实体类,就没法获取。
                    //我们可以通递归的方式去处理实体类包含实体类的问题。
                    if (types.contains(field.getType().getName())) {
                        if (field.getName().equals(fieldName)) {
                            value = field.get(object) == null ? "" : field.get(object).toString();
                            return value;
                        }
                    }
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    log.error("error {}",e);
                }
            }
            return value;
        }
    
        /**
         * 解析实体类,获取实体类中的属性
         *
         * @param object
         * @return
         */
        public static Object getFieldsValue(Object object) {
            //通过反射获取所有的字段,getFileds()获取public的修饰的字段
            //getDeclaredFields获取private protected public修饰的字段
            Field[] fields = object.getClass().getDeclaredFields();
            String typeName = object.getClass().getTypeName();
            if (types.contains(typeName)) {
                return object;
            }
            StringBuilder sb = new StringBuilder();
            sb.append("{");
            for (Field f : fields) {
                //在反射时能访问私有变量
                f.setAccessible(true);
                try {
                    //这边会有问题,如果实体类里面继续包含实体类,这边就没法获取。
                    //其实,我们可以通递归的方式去处理实体类包含实体类的问题。
                    if (types.contains(f.getType().getName())) {
                        sb.append(f.getName()).append(" : ").append(f.get(object)).append(", ");
                    }
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    log.error("error {}",e);
                }
            }
            sb.append("}");
            return sb.toString();
        }
    }
    

    2.3 也可以支持 不过我没有用而已 就是 解析表达式SPEL

    2.4 demo 是不是很简单

     /**
      *  id删除
     */
        @ApiOperation(value = "id删除",notes = "通用结果返回对象")
        @PostMapping("user/delete")
        @CacheKey(cacheEvent = CacheEvent.DELETE)
        public CommonResult<Boolean> delete(@ParameterCacheKey(fieldName = "id") User user){
            Boolean success = userService.removeById(user.getId());
            return CommonResultResponse.ok(success);
        }
    
        /**
        *  id查询
        */
        @ApiOperation(value = "id查询",notes = "通用结果返回对象")
        @GetMapping("user/findById")
        @CacheKey
        public CommonResult<User> findById(@ParameterCacheKey(fieldName = "id") Integer id, HttpServletResponse response, HttpServletRequest request, MultipartFile file){
            User user = userService.getById(id);
            return CommonResultResponse.ok(user);
        }
    

优点 可以省略缓存判断操作 单一职责的方法

缺点 带有多重缓存业务逻辑操作 还得自己灵活编写

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容