拒绝 try-catch:如何设计全局通用的异常拦截体系?

前言

在实际开发中,业务逻辑异常(如余额不足、权限受限)或系统异常(如空指针、数据库连接超时)无处不在。如果任由异常向上抛出,前端将会接收到含糊的 500 错误或敏感的代码堆栈信息。

我们不需要在每个 Controller 方法中写 try-catch,而是通过 @RestControllerAdvice 建立一套全局拦截机制,将所有捕获到的异常,统一转化为上一篇定义的 Result 响应体返回。

定义业务异常类

对于业务类异常,我们需要明确告知调用方具体的错误编码和提示信息。因此,在自定义业务异常(BusinessException)时,必须包含 code 字段,而 message 则直接复用父类 RuntimeException 的消息体系。

核心逻辑:

  • code :对应 Result 实体中的业务状态码,用于前端逻辑判断。
  • message:描述具体的异常原因,用于前端友好提示。

通过这种方式,我们可以将异常直接转化为标准化的 Result 响应。

这里我们用到上一节定义的 ResultCode 相关类来构建业务异常,# 手把手带你封装生产级 Result 实体

java 复制代码
@Getter
public class BusinessException extends RuntimeException {

    private final int code;

    /**
     * 使用默认错误码(1)构造
     *
     * @param message 错误信息
     */
    public BusinessException(String message) {
        super(message);
        this.code = CommonResultCode.BUSINESS_ERROR.getCode();
    }

    /**
     * 使用结果码接口构造
     *
     * @param resultCode 结果码接口
     */
    public BusinessException(ResultCode resultCode) {
        super(resultCode.getMessage());
        this.code = resultCode.getCode();
    }

    /**
     * 使用结果码接口构造,但覆盖错误信息
     *
     * @param resultCode 结果码接口
     * @param message    错误信息
     */
    public BusinessException(ResultCode resultCode, String message) {
        super(message);
        this.code = resultCode.getCode();
    }

    /**
     * 指定错误码构造
     *
     * @param code    错误码
     * @param message 错误信息
     */
    public BusinessException(int code, String message) {
        super(message);
        this.code = code;
    }

    /**
     * 包装其他异常
     *
     * @param message 错误信息
     * @param cause   原始异常
     */
    public BusinessException(String message, Throwable cause) {
        super(message, cause);
        this.code = CommonResultCode.BUSINESS_ERROR.getCode();
    }
}

全局异常处理器

定义好业务异常后,要使其能正常工作,需要配合全局异常处理器一起使用。在全局异常处理其中,除了普通的业务异常外,还要包含其他的如校验、服务器异常、404 等异常的处理:

java 复制代码
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 处理业务异常
     */
    @ExceptionHandler(BusinessException.class)
    public Result<Void> handleBusinessException(BusinessException e, HttpServletRequest request) {
        log.warn("业务异常: {} {}", request.getRequestURI(), e.getMessage());
        return Result.fail(e.getCode(), e.getMessage());
    }

    /**
     * 处理参数校验异常 (JSON Body)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result<Void> handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request) {
        String message = this.buildBindingResultMsg(e.getBindingResult());
        log.warn("参数校验异常: {} {}", request.getRequestURI(), message);
        return Result.fail(CommonResultCode.PARAM_ERROR.getCode(), message);
    }

    /**
     * 处理参数绑定异常 (Form Data)
     */
    @ExceptionHandler(BindException.class)
    public Result<Void> handleBindException(BindException e, HttpServletRequest request) {
        String message = this.buildBindingResultMsg(e.getBindingResult());
        log.warn("参数绑定异常: {} {}", request.getRequestURI(), message);
        return Result.fail(CommonResultCode.PARAM_ERROR.getCode(), message);
    }

    /**
     * 处理请求方法不支持异常
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public Result<Void> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        log.warn("请求方法不支持: {} {}, 支持的方法: {}", request.getMethod(), request.getRequestURI(), e.getSupportedHttpMethods());
        return Result.of(CommonResultCode.METHOD_NOT_ALLOWED);
    }

    /**
     * 处理兜底异常
     */
    @ExceptionHandler(Exception.class)
    public Result<Void> handleException(Exception e, HttpServletRequest request) {
        log.error("系统异常: " + request.getRequestURI(), e);
        return Result.of(CommonResultCode.SYSTEM_ERROR);
    }

    private String buildBindingResultMsg(BindingResult result) {
        return result.getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining("; "));
    }
}

有了这些配置,我们在业务中就可以通过抛异常的方式处理业务异常了!

相关推荐
打工的小王42 分钟前
Spring Boot(三)Spring Boot整合SpringMVC
java·spring boot·后端
80530单词突击赢2 小时前
JavaWeb进阶:SpringBoot核心与Bean管理
java·spring boot·后端
爬山算法3 小时前
Hibernate(87)如何在安全测试中使用Hibernate?
java·后端·hibernate
WeiXiao_Hyy3 小时前
成为 Top 1% 的工程师
java·开发语言·javascript·经验分享·后端
苏渡苇3 小时前
优雅应对异常,从“try-catch堆砌”到“设计驱动”
java·后端·设计模式·学习方法·责任链模式
long3163 小时前
Aho-Corasick 模式搜索算法
java·数据结构·spring boot·后端·算法·排序算法
rannn_1114 小时前
【苍穹外卖|Day4】套餐页面开发(新增套餐、分页查询、删除套餐、修改套餐、起售停售)
java·spring boot·后端·学习
短剑重铸之日4 小时前
《设计模式》第十一篇:总结
java·后端·设计模式·总结
Dragon Wu5 小时前
Spring Security Oauth2.1 授权码模式实现前后端分离的方案
java·spring boot·后端·spring cloud·springboot·springcloud
一个有梦有戏的人5 小时前
Python3基础:进阶基础,筑牢编程底层能力
后端·python