美文网首页
Aop实现注解限流和Redis缓存

Aop实现注解限流和Redis缓存

作者: JLLang | 来源:发表于2019-12-05 20:01 被阅读0次
    gratisography-plam-trees-summer

    JLLang https://my.oschina.net/jiansin

    限流注解实现

    业务系统中某些接口需要进行限流的时候在spring家族中可以采用RateLimiter进行接口限流,减轻服务器的压力。实现思路如下:

    RateLimit 注解

    /**
     * @description: 限流注解
     * @author: lilang
     * @version:
     * @modified By:1170370113@qq.com
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface RateLimit {
    
        double limitNum() default 20;  //默认每秒放入桶中的token
    
        //获取令牌的等待时间
        int timeOut() default 0;
    
        //等待时间单位
        TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
    }
    
    

    注解RateLimit AOP实现类

    @Component
    @Aspect
    public class RateLimitAspect {
    
        private Logger log = LoggerFactory.getLogger(this.getClass());
        //用来存放不同接口的RateLimiter(key为接口名称,value为RateLimiter)
        private ConcurrentHashMap<String, RateLimiter> map = new ConcurrentHashMap<>();
    
        private static ObjectMapper objectMapper = new ObjectMapper();
    
        private RateLimiter rateLimiter;
    
        @Pointcut("@annotation(com.itstyle.mail.common.aop.RateLimit)")
        public void serviceLimit() {
        }
    
        @ResponseBody
        @Around("serviceLimit()")
        public Object around(ProceedingJoinPoint joinPoint) throws NoSuchMethodException {
            Object obj = null;
            //获取拦截的方法名
            Signature sig = joinPoint.getSignature();
            //获取拦截的方法名
            MethodSignature msig = (MethodSignature) sig;
            //返回被织入增加处理目标对象
            Object target = joinPoint.getTarget();
            //为了获取注解信息
            Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
            //获取注解信息
            RateLimit annotation = currentMethod.getAnnotation(RateLimit.class);
            double limitNum = annotation.limitNum(); //获取注解每秒加入桶中的token
            TimeUnit timeUnit = annotation.timeUnit();//获取时间单位
            String functionName = msig.getName(); // 注解所在方法名区分不同的限流策略
            int timeOut = annotation.timeOut();
            
            //获取rateLimiter
            if(map.containsKey(functionName)){
                rateLimiter = map.get(functionName);
            }else {
                map.put(functionName, RateLimiter.create(limitNum));
                rateLimiter = map.get(functionName);
            }
    
            try {
                if (rateLimiter.tryAcquire(timeOut,timeUnit)) {
                    //执行方法
                    obj = joinPoint.proceed();
                } else {
                    return Result.error("服务器繁忙,请稍后再试....");
                }
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }
            return obj;
        }
    
    }
    

    使用方式 比如在controller层进行控制

    @RateLimit(limitNum = 10,timeOut = 1,timeUnit = TimeUnit.SECONDS)
    @GetMapping("limit/go")
    public void queryFromMysql(){
        //数据库操作逻辑
    }
    

    Redis缓存实现

    缓存注解定义:

    
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.METHOD)
    @Documented
    public @interface RedisCache {
    
        String prefix() default "";
        int expire() default 1;
        TimeUnit TIME_UNIT() default TimeUnit.DAYS;
        //缓存反序列化获取的对象
        Class clazz() default Object.class;
        //序列化后的对象是否是jsonarry 比如 List<Object>
        boolean isArray() default false;
    }
    
    

    缓存切面类实现:

    @Component
    @Aspect
    public class RedisCacheAspect {
    
        private static Logger logger = LoggerFactory.getLogger(RedisCacheAspect.class);
    
        @Autowired
        private RedisTemplate redisTemplate ;
    
        /**
         * 分隔符 生成key 格式为 类全类名|方法名|参数所属类全类名
         **/
        private static final String DELIMITER = "-";
    
        /**
         * Service层切点 使用到了我们定义的 RedisCacheAspect 作为切点表达式。
         * 而且我们可以看出此表达式基于 annotation。
         * 并且用于内建属性为查询的方法之上
         */
        @Pointcut("@annotation(com.itstyle.mail.common.aop.RedisCache)")
        public void redisCacheAspect() {
        }
    
        /**
         * Around 手动控制调用核心业务逻辑,以及调用前和调用后的处理,
         * <p>
         * 注意:当核心业务抛异常后,立即退出,转向AfterAdvice 执行完AfterAdvice,再转到ThrowingAdvice
         *
         */
        @Around(value = "redisCacheAspect()")
        public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
            // 得到类名、方法名和参数
            String clazzName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] args = joinPoint.getArgs();
    
            // 根据类名、方法名和参数生成Key
            logger.info("key参数: " + clazzName + "." + methodName);
            String key = getKey(clazzName, methodName, args);
            if (logger.isInfoEnabled()) {
                logger.info("生成key: " + key);
            }
    
            // 得到被代理的方法
            Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
    
            //redis 前缀
            String prefix = method.getAnnotation(RedisCache.class).prefix();
    
            int expire = method.getAnnotation(RedisCache.class).expire();
    
            TimeUnit timeUnit = method.getAnnotation(RedisCache.class).TIME_UNIT();
    
            Class objectType = method.getAnnotation(RedisCache.class).clazz();
    
            boolean isArray=method.getAnnotation(RedisCache.class).isArray();
    
            // 检查Redis中是否有缓存
            String value = (String) redisTemplate.opsForValue().get(prefix);
    
            // 得到被代理方法的返回值类型
            // Class returnType = ((MethodSignature) joinPoint.getSignature()).getReturnType();
    
            // result是方法的最终返回结果
            Object result = null;
            try {
                if (null == value) {
                    logger.info("缓存未命中");
                    // 调用数据库查询方法
                    result = joinPoint.proceed(args);
                    // 结果放入缓存
                    redisTemplate.opsForValue().set(prefix, JSON.toJSONString(result),expire,timeUnit);
                } else {
    
                    /**
                     * 可以直接针对mapper进行缓存,如果mapper查询返回的List<DemoObjec> 需要isArray 为true 否则转换异常
                     */
    
                    if (isArray){
                        return JSON.parseArray(value,objectType);
    
                    }else {
                        return JSON.parseObject(value,objectType);
                    }
                }
            } catch (Throwable e) {
                logger.error("程序异常",e.getMessage());
                throw e;
            }
            return result;
        }
    
        /**
         *      * 根据类名、方法名和参数生成Key
         *      * @param clazzName
         *      * @param methodName
         *      * @param args
         *      * @return key格式:全类名|方法名|参数类型
         *
         */
        private String getKey(String clazzName, String methodName, Object[] args) {
            StringBuilder key = new StringBuilder(clazzName);
            key.append(DELIMITER);
            key.append(methodName);
            key.append(DELIMITER);
            key.append(Arrays.stream(args).map(x->x.toString()).collect(Collectors.joining(DELIMITER)));
            return key.toString();
        }
    }
    
    

    使用方式 :

    以下是放在mapper层加入的注解,实际项目中可以根据自己的需求注解加在任意位置。

    @Mapper
    public interface DemoMapper {
    
        @RedisCache(expire = 1,clazz = DemoObject.class,isArray = true)
        public List<DemoObject> queryFromMysql();
    
        @RedisCache(expire = 1,clazz = DemoObject.class)
        public DemoObject queryFromMysql();
    }
    
    

    使用如上方式,我们便可以在实际项目中无侵入的实现业务限流和业务缓存。

    相关文章

      网友评论

          本文标题:Aop实现注解限流和Redis缓存

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