整体思路:
一 具体接口,可以自定义一个注解,配置限流量,然后对需要限流的方法加上注解即可!
二 容器初始化的时候扫描所有所有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个一起打印: