全局异常与局部捕获是分层协作关系:局部处理明确业务语义的异常并差异化响应,全局兜底未被捕获的意外异常,二者按优先级、职责分离、异常包装原则协同工作。

全局异常和局部捕获不是非此即彼的关系,而是分层协作:局部处理明确、可预期、需特殊响应的异常;全局兜底处理未被拦截的、意外的或跨层传播的异常。
局部捕获该用在哪儿
当某个接口有**明确业务语义的异常分支**,且需要差异化返回、提前终止流程或记录上下文时,就该用 try-catch。
- 例如参数校验失败后要返回特定错误码(如 40001)和字段级提示,而不是统一 400;
- 调用第三方服务超时,需降级返回缓存数据,而不是直接抛出异常交由全局处理;
- 数据库唯一约束冲突,想转成“用户名已存在”这种用户友好提示,而不是暴露 SQL 异常堆栈。
全局异常负责兜底和统一
@RestControllerAdvice 不是替代局部捕获,而是确保任何漏网的异常都有标准出口:
- Controller 层没 catch 的 RuntimeException(如空指针、类型转换失败);
- 拦截器、参数解析器、返回值处理器等 Spring MVC 内部组件抛出的异常(如 MethodArgumentNotValidException);
- 局部捕获后重新 throw 的异常,比如 catch 后做了日志但没处理完,仍向上抛,就会落到全局处理器。
配合的关键原则
避免重复处理和逻辑混乱,重点看三点:
- 优先级明确:局部 try-catch > @ExceptionHandler > 兜底 Exception 处理器;
- 职责分离:局部做“业务决策”,全局做“格式统一+安全兜底+日志归集”;
- 异常不裸抛:局部捕获后若需上抛,建议包装为自定义业务异常(如 BusinessException),便于全局按类型精准处理,而不是扔出原始 RuntimeException。
一个典型配合示例
用户注册接口中:
- 先用 @Valid 校验 DTO,失败触发 MethodArgumentNotValidException → 被全局捕获并转为规范错误响应;
- 查库发现手机号已存在 → 主动 throw new BusinessException(40002, "手机号已被注册") → 全局 @ExceptionHandler(BusinessException.class) 捕获并封装返回;
- 调用微信支付 SDK 时网络异常 → 局部 try-catch 捕获 IOException,记录 warn 日志并返回“支付通道繁忙”,不往上抛。


















