toomanyrequestsexception 应继承 runtimeexception,命名语义明确、构造函数支持多场景初始化,由限流拦截器触发抛出,再经 @controlleradvice 统一返回 429 响应及 retry-after 头。

自定义 TooManyRequestsException 的核心是:**明确语义、便于捕获、携带必要上下文、与限流框架协同良好**。不建议直接继承运行时异常就完事,而应围绕实际使用场景设计。
命名与继承关系要清晰
推荐继承 RuntimeException(非检查异常),因为限流失败属于业务流程中的预期异常分支,不应强制上层 try-catch。命名保持语义准确:
- 用
TooManyRequestsException符合 HTTP 429 状态码惯例,语义通用且易理解 - 避免模糊命名如
RateLimitException或FlowControlException(职责不唯一) - 不继承
IllegalArgumentException或IllegalStateException——它们表达的是参数/状态错误,而非资源受限
构造函数需支持多场景初始化
实际使用中,异常可能来自不同限流组件(如 Redis + Lua、Guava RateLimiter、Sentinel),需灵活传入关键信息:
-
基础构造:
TooManyRequestsException(String message) -
带响应头信息:
TooManyRequestsException(String message, int retryAfterSeconds, String limitKey)(方便后续填充Retry-After响应头) -
带原始原因:
TooManyRequestsException(String message, Throwable cause)(适配底层限流器抛出的封装异常)
示例:
public class TooManyRequestsException extends RuntimeException {
private final int retryAfterSeconds;
private final String limitKey;
public TooManyRequestsException(String message) {
super(message);
this.retryAfterSeconds = -1;
this.limitKey = null;
}
public TooManyRequestsException(String message, int retryAfterSeconds, String limitKey) {
super(message);
this.retryAfterSeconds = retryAfterSeconds;
this.limitKey = limitKey;
}
// getter 方法略(建议提供,便于统一处理)
}
与拦截逻辑解耦,由限流器触发
异常本身不负责判断是否超限,只作为“信号”被抛出。限流拦截器(如 Spring MVC 拦截器、Spring Cloud Gateway Filter、AOP 切面)在检测到拒绝请求时主动 throw:
- 不要在异常类里写限流算法或调用 Redis
- 拦截器中根据限流结果决定是否 new 并 throw 异常
- 例如:RedisLuaRateLimiter 尝试 acquire 失败 → 构造
TooManyRequestsException并抛出
全局异常处理器统一响应
配合 Spring 的 @ControllerAdvice,将该异常转为标准 HTTP 响应:
- 状态码设为
429 Too Many Requests - 添加
Retry-After响应头(值来自异常中的retryAfterSeconds) - 返回 JSON 提示(如
{"code": 429, "message": "请求过于频繁"})
这样前端或网关能按规范解析重试策略,而不是看到 500 或无头响应。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











