Java中通过自定义@RateLimit注解+RateLimiter+拦截器实现声明式防刷限流:注解定义QPS、超时等规则,拦截器解析注解并调用Guava RateLimiter校验令牌,限流器按方法级缓存复用,业务代码零侵入。

Java中通过自定义注解 + RateLimiter + 拦截器实现防刷限流,核心是把限流规则声明式地写在方法上,由拦截器统一解析、校验和拦截。关键不在“造轮子”,而在清晰分离:注解定义规则、拦截器执行判断、限流器(如 Guava 的 RateLimiter)提供底层令牌桶能力。
定义自定义限流注解
用 @interface 声明一个运行时保留、作用于方法的注解,支持配置 QPS(每秒请求数)、超时时间、拒绝提示等:
-
必须包含
value():表示每秒允许通过的请求数(如10) -
可选
timeout():获取令牌失败时等待的毫秒数(默认 0,即不等待直接拒绝) -
可选
msg():触发限流时返回的提示语(如“请求过于频繁,请稍后再试”)
示例:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
double value(); // QPS,如 5.0 表示每秒最多 5 次
long timeout() default 0; // 获取令牌超时毫秒数,0 表示非阻塞
String msg() default "操作太频繁,请稍后重试";
}
基于 Guava RateLimiter 实现限流管理器
Guava 的 RateLimiter 是线程安全、轻量级的令牌桶实现。注意:不能为每个接口创建独立的 RateLimiter 实例并长期持有(内存泄漏风险),应按接口或路径做缓存管理:
立即学习“Java免费学习笔记(深入)”;
- 用
ConcurrentMap<String, RateLimiter>缓存不同限流规则对应的限流器 - Key 可以是“类名.方法名”或自定义业务标识(如
user:login) - 首次访问时根据注解的
value()创建并缓存;后续复用
示例工具类:
public class RateLimitManager {
private static final ConcurrentMap<String, RateLimiter> LIMITERS = new ConcurrentHashMap<>();
public static RateLimiter getOrCreate(String key, double qps) {
return LIMITERS.computeIfAbsent(key, k -> RateLimiter.create(qps));
}
}
编写 Spring MVC 拦截器统一处理
继承 HandlerInterceptor,在 preHandle 中解析目标方法上的 @RateLimit 注解,获取限流参数,尝试获取令牌:
- 从
HandlerMethod提取@RateLimit注解 - 构造唯一 key(如
handlerMethod.getMethod().getDeclaringClass().getName() + "." + handlerMethod.getMethod().getName()) - 调用
RateLimiter.tryAcquire(timeout, TimeUnit.MILLISECONDS)判断是否放行 - 若失败,设置响应状态码(如 429 Too Many Requests)和 JSON 错误体,返回
false中断流程
示例拦截器片段:
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) return true;
HandlerMethod hm = (HandlerMethod) handler;
RateLimit annotation = hm.getMethodAnnotation(RateLimit.class);
if (annotation == null) return true;
String key = buildKey(hm);
RateLimiter limiter = RateLimitManager.getOrCreate(key, annotation.value());
boolean acquired = annotation.timeout() == 0
? limiter.tryAcquire()
: limiter.tryAcquire(annotation.timeout(), TimeUnit.MILLISECONDS);
if (!acquired) {
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write("{\"code\":429,\"msg\":\"" + annotation.msg() + "\"}");
return false;
}
return true;
}
注册拦截器并使用注解
在 Spring 配置类中注册该拦截器(如继承 WebMvcConfigurer),指定拦截路径(建议精确匹配需限流的 API):
- 避免对静态资源、健康检查等路径误拦截
- 可在
addInterceptors().addInterceptor(...).excludePathPatterns(...)中排除
在 Controller 方法上直接使用注解:
@PostMapping("/api/login")
@RateLimit(value = 3.0, timeout = 100, msg = "登录操作受限,请1分钟内最多尝试3次")
public Result login(@RequestBody LoginDTO dto) {
return userService.login(dto);
}
这样就完成了声明式限流——无需改业务逻辑,只需加个注解,拦截器自动生效。


















