美文网首页
优雅的使用redis给接口加缓存

优雅的使用redis给接口加缓存

作者: 任未然 | 来源:发表于2021-03-16 21:47 被阅读0次

    一. 概述

    在分布式系统中, 有些请求比较频繁的接口, 接口的返回结果又不经常变化或实时性要求不高, 为了避免频繁查询数据库, 就可以给接口加缓存. 本文将介绍利用代理的方式用redis实现加缓存, 使用时只需在方法上面加一个注解即可

    例如:

        @CacheData(keyName = "WPR_WPR",cacheTime = 3600)
        public Map<String,Object> getRedisMap(String param) throws IOException {
            HashMap<String, Object> map = new HashMap<>();
            map.put("hello","helloWorld");
            return map;
        }
    

    只需在方法上加上注解@CacheData, 即可实现加入缓存

    二. 实现原理

    2.1 加入缓存的注解

    @Target({ElementType.METHOD})  //作用的位置
    @Retention(RetentionPolicy.RUNTIME) //作用域
    @Documented
    public @interface CacheData {
        String keyName() default ""; // 缓存唯一KEY,默认类路径名+方法名
        long cacheTime() default 3600; // 缓存时间,默认1个小时
        String interfaceName() default ""; // 接口名称
        /**
         * 自定义参数序列化, 默认全部参数序列化, 数组填写从0开始
         * 例如: 方法有3个参数, Function(String param1,String param2,String param3)
         *       现在只想把第一和第三个参数作为KEY, 那么 paramIndex = {0,2}
         * @return
         */
        int[] paramIndex() default {};
    }
    

    2.2 清除缓存注解

    @Target({ElementType.METHOD})  //作用的位置
    @Retention(RetentionPolicy.RUNTIME) //作用域
    @Documented
    public @interface CacheClear {
        String[] keyName(); // 缓存唯一KEY
    }
    

    2.3 缓存代理实现

    @Aspect
    @Slf4j
    public class CacheAop {
    
        @Around(value = "@annotation(com.wpr.common.annotation.CacheClear)")
        public Object cacheClear(ProceedingJoinPoint pjp) throws Throwable {
            log.info("----------------缓存清除AOP开始--------------------");
            // 执行业务
            Object obj = pjp.proceed();
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            // 获取注解
            Method method = signature.getMethod();  //获取Method对象
            CacheClear cacheClear = method.getAnnotation(CacheClear.class);
            // 如果没有注解则直接返回
            if(null == cacheClear) return obj;
            // 缓存key
            String[] keyNames = cacheClear.keyName();
            if(ObjectUtils.isEmpty(keyNames)){
                keyNames = new String[] {pjp.getSignature().getDeclaringType().getName() + pjp.getSignature().getName()};
            }
            if(ObjectUtils.isEmpty(keyNames)) return obj;
            try {
                // 获取redis工具类
                RedisUtil redisUtil = (RedisUtil) SpringContextHolder.getBean(RedisUtil.class.getName());
                Arrays.stream(keyNames).forEach(redisKey -> {
                    log.info("清除缓存的KEY:"+redisKey);
                    // 删除缓存
                    redisUtil.deleteByPattern(redisKey+"*");
                });
            } catch (Exception e) {
                log.error("清除缓存报错:"+e.getMessage());
                log.error("清除缓存报错:"+e);
            }
    
            log.info("----------------缓存清除AOP结束--------------------");
            return obj;
        }
    
        @Around(value = "@annotation(com.wpr.common.annotation.CacheData)")
        public Object aroundCacheData(ProceedingJoinPoint pjp) throws Throwable {
            log.info("----------------缓存AOP执行开始--------------------");
            MethodSignature signature = (MethodSignature) pjp.getSignature();
            // 获取注解
            Method method = signature.getMethod();  //获取Method对象
            CacheData cacheData = method.getAnnotation(CacheData.class);
            // 如果没有注解则直接返回
            if(null == cacheData) return pjp.proceed();
            // 缓存key
            String keyName = cacheData.keyName();
            if(StringUtils.isEmpty(keyName)){
                log.info("目标方法所属类的名:" + pjp.getSignature().getDeclaringType().getName());
                log.info("目标方法名:" + pjp.getSignature().getName());
                keyName = pjp.getSignature().getDeclaringType().getName() + pjp.getSignature().getName();
            }
            log.info("传参缓存KEY:"+keyName);
            // 缓存时间
            long cacheTime = cacheData.cacheTime();
            log.info("传参缓存时间:"+cacheTime+"秒");
            // 参数
            Object[] args = pjp.getArgs();
            // 获取参数下标
            int[] paramIndex = cacheData.paramIndex();
            // 拼装缓存KEY
            AtomicReference<String> redisKey = new AtomicReference<>(keyName);
            if(!getRedisKey(args, keyName, paramIndex, redisKey)){
                return pjp.proceed();
            }
    
            Object obj = null;
            try {
                // 获取redis工具类
                RedisUtil redisUtil = (RedisUtil) SpringContextHolder.getBean(RedisUtil.class.getName());
                // 从缓存中获取数据
                obj = redisUtil.get(redisKey.get());
                if (null == obj) {
                    obj = pjp.proceed();
                    redisUtil.set(redisKey.get(), obj, cacheTime);
                }
            } catch (SerializationException e){
                log.error("缓存执行报错: 方法返回对象需实现接口Serialization!");
                obj = pjp.proceed();
            } catch (Throwable throwable) {
                log.error("缓存执行报错:"+throwable.getMessage());
                log.error("缓存执行报错:"+throwable);
                obj = pjp.proceed();
            }
            log.info("----------------缓存AOP执行结束--------------------");
            return obj;
        }
    }
    
        /**
         * 拼接redisKey
         * @param args
         * @param keyName
         * @param paramIndex
         * @param redisKey
         * @return
         */
        public boolean getRedisKey(Object[] args, String keyName, int[] paramIndex, AtomicReference<String> redisKey) {
            boolean flag = true;
            if (!ObjectUtils.isEmpty(args)) {
                if(!ObjectUtils.isEmpty(paramIndex)){
                    /**
                     * paramIndex的元素不能大于args的size
                     */
                    Object[] argsNew = new Object[paramIndex.length];
                    try {
                        for(int i = 0;i < paramIndex.length;i++){
                            int index = paramIndex[0];
                            Assert.isTrue(index < args.length,"paramIndex存在参数下标大于方法参数个数");
                            argsNew[i] = args[i];
                        }
                        flag = setRedisKey(argsNew, redisKey);
                    } catch (Exception e) {
                        log.error("拼接自定义参数序列化报错:"+e.getMessage());
                        log.error("拼接自定义参数序列化报错:"+e);
                        flag = false;
                    }
                }else {
                    flag = setRedisKey(args, redisKey);
                }
            }
            return flag;
        }
    
        public boolean setRedisKey(Object[] args, AtomicReference<String> redisKey) {
            boolean flag = true;
            try {
                String paramJson = JSON.toJSONString(args);
                log.info("方法参数:"+paramJson);
                StringBuffer key = new StringBuffer().append(redisKey.get()).append(":").append(paramJson);
                redisKey.set(key.toString());
            } catch (Throwable e) {
                log.error("方法参数传JSON,报错信息:"+e.getMessage());
                log.error("方法参数传JSON,报错堆栈信息:"+e);
                flag = false;
            }
            return flag;
        }
    

    2.4 redis全局配置

    @Configuration
    public class RedisConfig {
        /**
         * 设置redisTemplate的序列化方式
         * @param factory
         * @return
         */
        @Bean
        public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
            RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
            //  注册到系统中去
            redisTemplate.setConnectionFactory(factory);
            redisTemplate.setValueSerializer(RedisSerializer.json());
            redisTemplate.setHashValueSerializer(RedisSerializer.json());
            redisTemplate.setKeySerializer(RedisSerializer.string());
            redisTemplate.setHashKeySerializer(RedisSerializer.string());
            return redisTemplate;
        }
    }
    

    2.4 redis工具类

    @Component
    public class RedisUtil  {
        @Resource
        private RedisTemplate<String, Object> redisTemplate;
    
        /**
         * 设置指定 key 的值
         *
         * @param key     键
         * @param value   值
         * @param seconds 时间(秒) seconds要大于0 如果seconds小于等于0 将设置无限期
         * @return true成功 false 失败
         */
        public boolean set(String key, Object value, long seconds) {
            try {
                if (seconds == 0) {
                    redisTemplate.opsForValue().set(key, value);
                } else {
                    redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
                }
                return true;
            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
        }
    
        /**
         * 设置指定 key 的值
         * 
         * @param key     键
         * @return 
         */
        public Object get(String key) {
            return redisTemplate.opsForValue().get(key);
        }
    
        /**
         * 模糊匹配批量删除
         *
         * @param pattern 匹配的前缀
         */
        public void deleteByPattern(String pattern) {
            Set<String> keys = redisTemplate.keys(pattern);
            if (!CollectionUtils.isEmpty(keys)) {
                redisTemplate.delete(keys);
            }
        }
    }
    

    2.5 使用示例

    • 添加缓存
        @CacheData(keyName = "WPR",cacheTime = 3600)
        public Map<String,Object> getRedisMap(String param) throws IOException {
            HashMap<String, Object> map = new HashMap<>();
            map.put("hello","helloWorld");
            return map;
        }
    
    • 清除缓存
        @CacheClear(keyName = "WPR")
        public void cacheClear(){
    
        }
    

    相关文章

      网友评论

          本文标题:优雅的使用redis给接口加缓存

          本文链接:https://www.haomeiwen.com/subject/dfrjcltx.html