# spring-boot-demo-ratelimit-guava > 此 demo 主要演示了 Spring Boot 项目如何通过 AOP 结合 Guava 的 RateLimiter 实现限流,旨在保护 API 被恶意频繁访问的问题。 ## 1. 主要代码 ### 1.1. pom.xml ```xml 4.0.0 spring-boot-demo-ratelimit-guava 1.0.0-SNAPSHOT jar spring-boot-demo-ratelimit-guava Demo project for Spring Boot com.xkcoding spring-boot-demo 1.0.0-SNAPSHOT UTF-8 UTF-8 1.8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop cn.hutool hutool-all com.google.guava guava org.springframework.boot spring-boot-starter-test test org.projectlombok lombok true spring-boot-demo-ratelimit-guava org.springframework.boot spring-boot-maven-plugin ``` ### 1.2. 定义一个限流注解 `RateLimiter.java` > 注意代码里使用了 `AliasFor` 设置一组属性的别名,所以获取注解的时候,需要通过 `Spring` 提供的注解工具类 `AnnotationUtils` 获取,不可以通过 `AOP` 参数注入的方式获取,否则有些属性的值将会设置不进去。 ```java /** *

* 限流注解,添加了 {@link AliasFor} 必须通过 {@link AnnotationUtils} 获取,才会生效 * * @author yangkai.shen * @date Created in 2019-09-12 14:14 * @see AnnotationUtils *

*/ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface RateLimiter { int NOT_LIMITED = 0; /** * qps */ @AliasFor("qps") double value() default NOT_LIMITED; /** * qps */ @AliasFor("value") double qps() default NOT_LIMITED; /** * 超时时长 */ int timeout() default 0; /** * 超时时间单位 */ TimeUnit timeUnit() default TimeUnit.MILLISECONDS; } ``` ### 1.3. 定义一个切面 `RateLimiterAspect.java` ```java /** *

* 限流切面 *

* * @author yangkai.shen * @date Created in 2019-09-12 14:27 */ @Slf4j @Aspect @Component public class RateLimiterAspect { private static final ConcurrentMap RATE_LIMITER_CACHE = new ConcurrentHashMap<>(); @Pointcut("@annotation(com.xkcoding.ratelimit.guava.annotation.RateLimiter)") public void rateLimit() { } @Around("rateLimit()") public Object pointcut(ProceedingJoinPoint point) throws Throwable { MethodSignature signature = (MethodSignature) point.getSignature(); Method method = signature.getMethod(); // 通过 AnnotationUtils.findAnnotation 获取 RateLimiter 注解 RateLimiter rateLimiter = AnnotationUtils.findAnnotation(method, RateLimiter.class); if (rateLimiter != null && rateLimiter.qps() > RateLimiter.NOT_LIMITED) { double qps = rateLimiter.qps(); if (RATE_LIMITER_CACHE.get(method.getName()) == null) { // 初始化 QPS RATE_LIMITER_CACHE.put(method.getName(), com.google.common.util.concurrent.RateLimiter.create(qps)); } log.debug("【{}】的QPS设置为: {}", method.getName(), RATE_LIMITER_CACHE.get(method.getName()).getRate()); // 尝试获取令牌 if (RATE_LIMITER_CACHE.get(method.getName()) != null && !RATE_LIMITER_CACHE.get(method.getName()).tryAcquire(rateLimiter.timeout(), rateLimiter.timeUnit())) { throw new RuntimeException("手速太快了,慢点儿吧~"); } } return point.proceed(); } } ``` ### 1.4. 定义两个API接口用于测试限流 ```java /** *

* 测试 *

* * @author yangkai.shen * @date Created in 2019-09-12 14:22 */ @Slf4j @RestController public class TestController { @RateLimiter(value = 1.0, timeout = 300) @GetMapping("/test1") public Dict test1() { log.info("【test1】被执行了。。。。。"); return Dict.create().set("msg", "hello,world!").set("description", "别想一直看到我,不信你快速刷新看看~"); } @GetMapping("/test2") public Dict test2() { log.info("【test2】被执行了。。。。。"); return Dict.create().set("msg", "hello,world!").set("description", "我一直都在,卟离卟弃"); } } ``` ## 2. 测试 - test1 接口未被限流的时候 image-20190912155209716 - test1 接口频繁刷新,触发限流的时候 image-20190912155229745 - test2 接口不做限流,可以一直刷新 image-20190912155146012 ## 3. 参考 - [限流原理解读之guava中的RateLimiter](https://juejin.im/post/5bb48d7b5188255c865e31bc) - [使用Guava的RateLimiter做限流](https://my.oschina.net/hanchao/blog/1833612)