@Aspect @Component public class RateLimiterAop { // 创建map,key是URL,value是令牌,同一个请求就覆盖上一个值 private static ConcurrentHashMap<String, RateLimiter> rateLimiterMap = new ConcurrentHashMap<String, RateLimiter>(); //切入点,拦截com.lzh.demo包下的所有的public类和方法 @Pointcut("execution(public * com.lzh.demo.*(..))") public void rlAop() { } //使用AOP环绕通知判断拦截所有springmvc的请求,判断请求方法上是否存在ExtRateLimiter @Around("rlAop()") public Object doBefore(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { //判断是否存在@ExtRateLimiter注解 MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature(); // 使用Java反射技术获取方法上是否有@ExtRateLimiter注解类 ExtRateLimiter extRateLimiter = signature.getMethod().getDeclaredAnnotation(ExtRateLimiter.class); if (extRateLimiter == null) { // 直接进入正常方法 Object proceed = proceedingJoinPoint.proceed(); return proceed; }// 获取配置的速率,默认值 double value = extRateLimiter.value(); // 获取等待令牌等待时间 long timeOut = extRateLimiter.timeOut(); RateLimiter rateLimiter = getRateLimiter(value, timeOut); // 如果没有在有效期内获得token,那么就调用降级方法 boolean tryAcquire = rateLimiter.tryAcquire(timeOut, TimeUnit.MILLISECONDS); if (!tryAcquire) { serviceDowng(); return null; } // 获取到令牌,直接执行 Object proceed = proceedingJoinPoint.proceed(); return proceed; } // 获取RateLimiter对象,保证每个请求都是单例的,比如很多的同一个pay请求可以共用一个ratelimiter,
//但是如果是order请求,那么就必须再创建一个ratelimiter,根据URL进行判断 private RateLimiter getRateLimiter(double value, long timeOut) { // 获取当前URI ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request = attributes.getRequest(); String requestURI = request.getRequestURI(); RateLimiter rateLimiter = null; if (!rateLimiterMap.containsKey(requestURI)) { // 如果没有检测到URI,那么创建一个新的ratelimiter rateLimiter = RateLimiter.create(value); // 独立线程 rateLimiterMap.put(requestURI, rateLimiter); } else { // 能够在map中检测到URL就添加到map中,相同的请求在同一个ratemiliter中 rateLimiter = rateLimiterMap.get(requestURI); } return rateLimiter; } // 服务降级 private void serviceDowng() throws IOException {
// 获取响应 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletResponse response = attributes.getResponse(); response.setHeader("Content-type", "text/html;charset=UTF-8"); PrintWriter writer = response.getWriter(); try { writer.println("执行降级方法,亲,服务器忙!请稍后重试!"); } catch (Exception e) { } finally { writer.close(); } } }
@Target(value = ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface ExtRateLimiter {
// 以秒为单位的速率往桶中存令牌 double value(); // 规定的毫秒内如果没有得到令牌那么就走降级服务 long timeOut(); }
package com.lzh.demo
// 测试代码
@RequestMapping("/myOrder") @ExtRateLimiter(value = 10.0, timeOut = 500) public String myOrder() throws InterruptedException { System.out.println("myOrder"); return "SUCCESS"; }