本文目录导读:

在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,但所有异常都得到统一、友好、规范的响应。