Java异常捕获流程如何统一

wen java案例 28

本文目录导读:

Java异常捕获流程如何统一

  1. 统一响应体(Response Body)
  2. 自定义异常类与错误码枚举
  3. 全局异常处理器(核心)
  4. 使用流程
  5. 高级技巧
  6. 统一异常流程的核心原则

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

抱歉,评论功能暂时关闭!