zoukankan      html  css  js  c++  java
  • 使用AOP和Semaphore对项目中具体的某一个接口进行限流

    整体思路:

    一 具体接口,可以自定义一个注解,配置限流量,然后对需要限流的方法加上注解即可!

    二 容器初始化的时候扫描所有所有controller,并找出需要限流的接口方法,获取对应的限流量

    三 使用拦截器或者aop,对加上注解的方法进行限流,采用配置的信号量

    自定义注解

    /**
     * 限流注解
     */
    @Target(ElementType.METHOD)  //作用与方法上
    @Retention(RetentionPolicy.RUNTIME) //注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在
    @Documented
    public @interface ApiRateLimit {
        int value(); //控制并发最大数量
    }
    

      

    出初始化限流配置,注意:这里设置的如果限流量一样,则两个方法一起限流,比如两个方法限流量都是5,则两个方法总共可以支持最多5个线程访问

    实际可以自己调整,加个方法名当key,则可以保证每个方法都独自限流:

    /**
     *  ApplicationContextAware实现类可以获得spring上下文
     *   间接获取ApplicationContext中的所有bean,向切面添加所有接口的配置的限流量
     */
    @Component
    public class InitApiLimit implements ApplicationContextAware {
        @Override
        public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
            Map<String, Object> beanMap = applicationContext.getBeansWithAnnotation(RestController.class);
            System.out.println(beanMap.size());
            beanMap.forEach((k,v)->{
                Class<?> controllerClass = v.getClass();
                System.out.println(controllerClass.toString());
                System.out.println(controllerClass.getSuperclass().toString());
                //获取所有声明的方法
                Method[] allMethods = controllerClass.getSuperclass().getDeclaredMethods();
                for (Method method:allMethods){
                    System.out.println(method.getName());
                    //判断方法是否使用了限流注解
                    if (method.isAnnotationPresent(ApiRateLimit.class)){
                        //获取配置的限流量,实际值可以动态获取,配置key,根据key从配置文件获取
                        int value = method.getAnnotation(ApiRateLimit.class).value();
                        String key = String.valueOf(value);
                        //key作为key.value为具体限流量,传递到切面的map中
                        ApiLimitAspect.semaphoreMap.put(key,new Semaphore(value));
                    }
                }
                System.out.println("----信号量个数:"+ApiLimitAspect.semaphoreMap.size());
            });
        }
    }
    

      注意:这里有一点需要说明,一旦使用了代理,因为是controller',没有借口,所以是cglib,会创建子类

    ,此时从容器中获取的是代理的子类,默认是不会有自定义注解的,所以得getSuperClass,从父类,即controller中获取注解信息

    编写切面,这里是最主要的,使用jdk自带的信号量:

    限流切面

    /**
     * 限流切面
     */
    @Aspect
    @Order(value = Ordered.HIGHEST_PRECEDENCE) //最高优先级
    @Component
    public class ApiLimitAspect {
        //存储限流量和方法,必须是static且线程安全,保证所有线程进入都唯一
        public static Map<String, Semaphore> semaphoreMap= new ConcurrentHashMap<>();
        //拦截所有controller 的所有方法
        @Around("execution(* com.hou.serviceorder.controller.*.*(..))")
        public Object around(ProceedingJoinPoint joinPoint){
            Object result=null;
            Semaphore semaphore=null;
            Class<?> clz = joinPoint.getTarget().getClass();//获取目标对象
            Signature signature = joinPoint.getSignature();//获取增强方法信息
            String name = signature.getName();
            String limitKey = String.valueOf(getLimitKey(clz, name));
            if(limitKey!=null && !"".equals(limitKey)){
                semaphore = semaphoreMap.get(limitKey);
                try {
                    semaphore.acquire();
                    result=joinPoint.proceed();
                } catch (Throwable e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }
            return result;
        }
    
        //获取拦截方法配置的限流key,没有返回null
        private Integer getLimitKey(Class<?> clz, String methodName){
            for (Method method:clz.getDeclaredMethods()){
                if(method.getName().equals(methodName)){//找出目标方法
                    if(method.isAnnotationPresent(ApiRateLimit.class)){//判断是否是限流方法
                        return method.getAnnotation(ApiRateLimit.class).value();
                    }
                }
            }
            return null;
        }
    }
    

      

    使用注解

        @ApiRateLimit(value = 5)
        @GetMapping("/name")
        public String getOrderName() throws InterruptedException {
            System.out.println("-----进入getOrder方法------");
            TimeUnit.SECONDS.sleep(2);
            return "order";
        }
    
        @ApiRateLimit(value = 5)
        @GetMapping("/order")
        public Order getOrder(String id) throws InterruptedException {
            System.out.println("-----进入getOrder方法------");
            TimeUnit.SECONDS.sleep(2);
            return new Order(id,"侯征");
        }
    

      

    测试

    public class TestSe {
    
        public static void main(String[] args) {
            //测试信号并发量
            for (int i = 0; i < 10; i++) {
                new Thread(()->{
                    //访问目标接口
                    RestTemplate restTemplate = new RestTemplate();
                    restTemplate.getForObject("http://localhost:8081/order/name",String.class);
                }).start();
            }
        }
    }
    

      会发现最多5个一起打印:

  • 相关阅读:
    OSPF
    【今日CS 视觉论文速览】 24 Jan 2019
    【今日CS 视觉论文速览】Wed, 23 Jan 2019
    【今日CS 视觉论文速览】 21 Jan 2019
    【Processing学习笔记】安装与入门
    【今日CS 视觉论文速览】Part2, 18 Jan 2019
    【今日CS 视觉论文速览】Fri, 18 Jan 2019
    【今日CS 视觉论文速览】Thu, 17 Jan 2019
    【今日CS 视觉论文速览】Part2, 16 Jan 2019
    【今日CS 视觉论文速览】Wed, 16 Jan 2019
  • 原文地址:https://www.cnblogs.com/houzheng/p/10817175.html
Copyright © 2011-2022 走看看