<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.3.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author cook
* @Description 自定义一个限流注解
* @date 2023/11/5 8:36
* @email 3293336923@qq.com
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MarcoCurrentLimit {
/*
name: 限流的名称
token: 每秒生成的token 数量
*/
String name() default "";
double token() default 10;
}
AOP:
import com.google.common.util.concurrent.RateLimiter;
import com.mayikt.annotation.MarcoCurrentLimit;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author cook
* @Description
* @date 2023/11/5 17:48
* @email 3293336923@qq.com
*/
@Aspect
@Component
public class CurrentLimitAOP {
// private RateLimiter rateLimiter = RateLimiter.create(1.0);
// 如果是多个请求方法,要设置成不同的QPS,应该怎么办?
// 答:其实就是设置多个 RateLimiter 的参数
ConcurrentHashMap<String , RateLimiter> rateLimiters = new ConcurrentHashMap<>();
/**
* 只要方法上加上该自定义的限流注解,就会被aop环绕通知
* @param joinPoint
* @return
*/
@Around(value = "@annotation(com.mayikt.annotation.MarcoCurrentLimit)")
public Object around(ProceedingJoinPoint joinPoint) {
// 获取拦截签名对象
Signature signature = joinPoint.getSignature();
// 获取被拦截的方法签名信息对象
MethodSignature methodSignature = (MethodSignature) signature;
// 获取被拦截的方法对象
Method methodObject = methodSignature.getMethod();
// 获取自定义注解 MarcoCurrentLimit 上的 name 和 token的 对应的 value 值
MarcoCurrentLimit mayiktCurrentLimit = methodObject.getAnnotation(MarcoCurrentLimit.class);
// 获取该注解的 name 和 token 值
String name = mayiktCurrentLimit.name();
double token = mayiktCurrentLimit.token();
// 环绕方法开始执行
System.out.println("①、环绕方法开始执行!");
/***
* 如果是多个请求方法,要设置成不同的QPS,应该怎么办?
* 答:其实就是设置多个 RateLimiter 的参数,其实就是可以创建多个 RateLimiter对象
*/
try{
// 根据 name 去 集合中查看是否有 RateLimter 对象,如果没有就进行创建,并且去放到 集合中以便于下一周期的使用
RateLimiter rateLimiter = rateLimiters.get(name);
if (rateLimiter == null) {
rateLimiter = RateLimiter.create(token);
rateLimiters.put(name , rateLimiter);
}
// 判断是否可以获取到访问的token
boolean isAccessToken = rateLimiter.tryAcquire();
if (!isAccessToken) {
return "当前访问人的次数过多,请稍后重试!";
}
// 当代码执行到这里,说明访问成功,给用户展示数据----其实调用目标方法,然后进行返回
Object proceedResult = joinPoint.proceed();
return proceedResult;
} catch (Throwable throwable) {
return "系统错误";
}
}
}
测试环境:
import com.mayikt.annotation.MarcoCurrentLimit;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author cook
* @Description
* @date 2023/11/2 23:50
* @email 3293336923@qq.com
*/
@RestController
public class QPSController {
// 一秒钟生成 1 一个getInfo 访问令牌
@MarcoCurrentLimit(name = "getInfo" , token = 1)
@GetMapping("/getInfo")
public String getInfo(){
return "Please start your operation!----------getInfo()............";
}
// 一秒钟生成 10个 get 访问令牌
@MarcoCurrentLimit(name = "get", token = 10 )
@GetMapping("/get")
public String get(){
return "Please start your operation!----------get1()............";
}
}
运行流程:
当我自定义一个注解,参数为 name 访问令牌 , token 一秒内访问次数 ,
我通过 AOP 对这个注解进行拦截,当我拦截到这个注解所对应的方法后,我就获取到当前方法所对应这个注解 的 name 和 token,他们作为 当前执行方法的 限流名称和访问次数。