@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";
}