本文目录导读:

在Java开发中,统一异常捕获流程的核心目标是将业务逻辑与异常处理逻辑分离,避免代码中充斥大量的 try-catch 块,同时提供一致、友好的错误响应。
最主流的标准做法是:Spring Boot + 全局异常处理器 + 自定义异常 + 统一响应体。
下面分步说明如何构建一个统一且优雅的异常捕获流程。
统一响应体(Response Body)
这是所有正常和异常响应的统一格式,前端可以依靠固定的字段进行解析。
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResult<T> {
private int code; // 业务状态码(非HTTP状态码)
private String msg; // 提示信息
private T data; // 数据主体
// 快速构建成功/失败的方法
public static <T> ApiResult<T> success(T data) {
return new ApiResult<>(200, "success", data);
}
public static <T> ApiResult<T> fail(int code, String msg) {
return new ApiResult<>(code, msg, null);
}
// 支持从异常类型自动获取状态码
public static <T> ApiResult<T> fail(ICodeEnum codeEnum) {
return new ApiResult<>(codeEnum.getCode(), codeEnum.getMsg(), null);
}
}
自定义异常类与错误码枚举
通常将业务错误码定义为枚举,并让自定义异常携带该枚举。
// 错误码接口
public interface ICodeEnum {
int getCode();
String getMsg();
}
// 错误码枚举
public enum BizCodeEnum implements ICodeEnum {
SUCCESS(200, "操作成功"),
PARAM_ERROR(400, "参数错误"),
NOT_FOUND(404, "资源未找到"),
SYSTEM_ERROR(500, "系统繁忙"),
TOKEN_EXPIRED(401, "Token已过期");
private final int code;
private final String msg;
// 构造方法
}
// 自定义业务异常
public class BizException extends RuntimeException {
private final int code;
private final String msg;
public BizException(ICodeEnum codeEnum) {
super(codeEnum.getMsg());
this.code = codeEnum.getCode();
this.msg = codeEnum.getMsg();
}
public BizException(int code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
// getter 方法...
}
全局异常处理器(核心)
使用 @RestControllerAdvice(或 @ControllerAdvice)捕获所有控制器层抛出的异常,统一转换为 ApiResult。
@RestControllerAdvice
public class GlobalExceptionHandler {
// 1. 处理自定义业务异常
@ExceptionHandler(BizException.class)
public ApiResult<Void> handleBizException(BizException e) {
return ApiResult.fail(e.getCode(), e.getMsg());
}
// 2. 处理参数校验异常(@Valid + @RequestBody)
@ExceptionHandler(MethodArgumentNotValidException.class)
public ApiResult<Void> handleValidation( MethodArgumentNotValidException e) {
String msg = e.getBindingResult().getAllErrors().stream()
.map(DefaultMessageSourceResolvable::getDefaultMessage)
.collect(Collectors.joining("; "));
return ApiResult.fail(BizCodeEnum.PARAM_ERROR.getCode(), msg);
}
// 3. 处理参数绑定异常(如 @RequestParam 类型错误)
@ExceptionHandler(ConstraintViolationException.class)
public ApiResult<Void> handleConstraintViolation(ConstraintViolationException e) {
String msg = e.getConstraintViolations().stream()
.map(cv -> cv.getPropertyPath() + ": " + cv.getMessage())
.collect(Collectors.joining("; "));
return ApiResult.fail(BizCodeEnum.PARAM_ERROR.getCode(), msg);
}
// 4. 处理HTTP相关的404/405等 (Spring Web)
@ExceptionHandler(NoHandlerFoundException.class)
public ApiResult<Void> handleNoHandler(NoHandlerFoundException e) {
return ApiResult.fail(BizCodeEnum.NOT_FOUND.getCode(), "接口不存在");
}
// 5. 处理HTTP请求方法不支持
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
public ApiResult<Void> handleMethodNotSupported(HttpRequestMethodNotSupportedException e) {
return ApiResult.fail(405, "请求方法不支持: " + e.getMethod());
}
// 6. 兜底:处理所有未捕获的异常(最后一道防线)
@ExceptionHandler(Exception.class)
public ApiResult<Void> handleException(Exception e, HttpServletRequest request) {
// 记录详细日志(很重要)
log.error("请求: {} 发生未捕获异常", request.getRequestURI(), e);
return ApiResult.fail(BizCodeEnum.SYSTEM_ERROR.getCode(), "系统繁忙,请稍后重试");
}
}
使用流程
Controller 中不再需要大量 try-catch,只需抛出异常即可:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public ApiResult<User> getUser(@PathVariable Long id) {
if (id <= 0) {
// 直接抛出业务异常
throw new BizException(BizCodeEnum.PARAM_ERROR);
}
User user = userService.findById(id);
if (user == null) {
throw new BizException(BizCodeEnum.NOT_FOUND);
}
return ApiResult.success(user);
}
@PostMapping
public ApiResult<User> createUser(@Valid @RequestBody UserCreateDTO dto) {
// dto 校验失败,会自动抛出 MethodArgumentNotValidException
User user = userService.create(dto);
return ApiResult.success(user);
}
}
高级技巧
A. 区分业务异常与系统异常
- 业务异常(
BizException):给用户看,返回友好提示(如“密码错误”)。 - 系统异常(
Exception):记录详细日志,返回通用提示(如“系统繁忙”),绝不暴露堆栈给前端。
B. 处理过滤器中抛出的异常
全局 @ExceptionHandler 只能捕获 Controller 层的异常。Filter 中抛出的异常需要额外处理:
@Component
public class AuthFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain)
throws ServletException, IOException {
try {
// 鉴权逻辑,如果失败抛出BizException
chain.doFilter(request, response);
} catch (BizException e) {
// 手动写入统一格式
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(
new ObjectMapper().writeValueAsString(
ApiResult.fail(e.getCode(), e.getMsg())
)
);
}
}
}
或者采用 ErrorController 方式统一处理 /error 端点的返回。
C. 使用 @ControllerAdvice + 优先级控制
@Order(Ordered.HIGHEST_PRECEDENCE) // 提高优先级
@RestControllerAdvice
public class SpecificExceptionHandler {
// 处理特定模块的异常
}
统一异常流程的核心原则
| 原则 | 说明 |
|---|---|
| 单一职责 | Controller 只做参数校验和调用,不处理异常逻辑 |
| 分类处理 | 业务异常用自定义异常,系统异常用顶级 Exception 兜底 |
| 统一响应 | 所有异常响应都走同一个 ApiResult 结构 |
| 日志完整 | 系统异常一定要记录堆栈,便于排查 |
| 隐藏细节 | 不向前端暴露堆栈、数据库错误、内部路径等敏感信息 |
通过以上步骤,你的 Java 项目就可以实现:
代码中没有
try-catch,但所有异常都得到统一、友好、规范的响应。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播