统一拦截异常 @RestControllerAdvice

相关内容来自腾讯元宝, 作者也属于入门,仅做参考

我们学习了异常, 除了具体业务里针对不同的异常, 我们还可以统一拦截异常

创建自定义异常

前面我们学习了异常类的知识, 有个知识点叫抛出自定义异常, 如何定义呢, 我们定义一个粗略的业务异常

java 复制代码
public class BusinessException extends Exception {
	public BusinessException(String message) {
		super(message);
	}
}

这个自定义的异常就是要继承Exception或其他Exception的子类, 我们这个类就继承了异常类的特性.

比如订单,金额,是否为空等, 写到不满足业务条件 我们就可以throw

java 复制代码
throw new BusinessException("这是一个自定义的异常")

Throwable

├── Exception

│ ├── RuntimeException

│ │ ├── DataAccessException (Spring)

│ │ │ ├── DuplicateKeyException

│ │ │ ├── DataIntegrityViolationException

│ │ │ └── ...

│ │ ├── IllegalArgumentException

│ │ └── ...

│ ├── IOException

│ └── ...

└── Error

下面是继承的Exception

自定义一个异常类来 处理运行时的异常

java 复制代码
package com.example.demo.autil;

import lombok.Data;

/*
@Data相当于以下注解的组合:
@Getter:生成所有字段的 getter 方法
@Setter:生成所有字段的 setter 方法(final 字段除外)
@ToString:生成 toString() 方法
@EqualsAndHashCode:生成 equals() 和 hashCode() 方法
@RequiredArgsConstructor:生成包含 final 字段和 @NonNull 字段的构造方法
 */
@Data
public class AppBusinessException extends Exception{
    private int code;
    private String message;
    public AppBusinessException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public AppBusinessException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }
}

下面是继承的RuntimeException

java 复制代码
package com.example.demo.autil;

/**
 * 自定义异常类 需要继承 RuntimeException 然后需要构造函数
 */
public class AppException extends RuntimeException {
    private int code;
    private String message;
    public AppException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public AppException(int code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

下面是个通用的统一拦截异常

@RestControllerAdvice 注解

java 复制代码
package com.example.demo.autil;

import com.example.demo.aenum.ResultCode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.example.demo.autil.ResultUtil;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

/**
 * 全局捕获异常, 这个类可以监听服务器,数据库操作,自定义异常等
 */

@RestControllerAdvice
public class GlobalExceptionHandler {

    private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    /**
     * 处理所有传参异常
     */
    @ExceptionHandler(MissingServletRequestParameterException.class)
    public ResponseEntity<?> handleException(MissingServletRequestParameterException e) {

        String message = String.format("缺少必需参数: %s (类型: %s)",
                e.getParameterName(), e.getParameterType());

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed(message));
    }

    /**
     * 处理所有业务的异常
     */
    @ExceptionHandler(value = AppBusinessException.class)
    public ResponseEntity<?> handleException(AppBusinessException e) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed(e.getMessage()));
    }

    /**
     * 处理所有SQL异常
     */
    @ExceptionHandler(Exception.class)
    public ResponseEntity<?> handleException(Exception e) {
        logger.error("系统异常---: ", e);

        // 自定义异常--
        if (e instanceof AppException) {
            AppException appException = (AppException) e;
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ResultUtil.failed(appException.getCode(), appException.getMessage()));
        }
        // 根据异常类型返回不同的错误信息
        if (e instanceof DataAccessException) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ResultUtil.failed("数据库操作失败,请稍后重试"));
        }

        if (e instanceof RuntimeException) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                    .body(ResultUtil.failed("系统运行异常"));
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed("系统内部错误"));
    }

    /**
     * 专门处理数据访问异常
     */
    @ExceptionHandler(DataAccessException.class)
    public ResponseEntity<?> handleDataAccessException(DataAccessException e) {
        logger.error("数据库访问异常: ", e);

        // 获取根异常信息
        Throwable rootCause = getRootCause(e);
        String errorMessage = "数据库操作失败";

        if (rootCause != null) {
            // 根据具体的SQL异常类型返回更详细的错误信息
            // 这个提示是根据唯一索引提示的
            if (rootCause.getMessage().contains("Duplicate entry")) {
                errorMessage = "数据已存在,请勿重复添加";
            } else if (rootCause.getMessage().contains("Data too long")) {
                errorMessage = "数据长度超过限制";
            } else if (rootCause.getMessage().contains("cannot be null")) {
                errorMessage = "必填字段不能为空";
            } else if (rootCause.getMessage().contains("foreign key constraint")) {
                errorMessage = "存在关联数据,无法删除";
            }
        }

        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body(ResultUtil.failed(errorMessage));
    }

    private Throwable getRootCause(Throwable throwable) {
        Throwable cause = throwable;
        while (cause.getCause() != null) {
            cause = cause.getCause();
        }
        return cause;
    }
}

注意到: @ExceptionHandler(Exception.class) 和 @ExceptionHandler(DataAccessException.class) 冲突么?

不会冲突!​ Spring 会按照最精确匹配的原则执行异常处理。

@ExceptionHandler 注解的执行顺序和冲突处理

核心原则:精确匹配优先

下面例子写一个几种捕获异常的情况

java 复制代码
   @GetMapping("getSystemError")
    public Result<?> getSystemError(@RequestParam String type, @RequestParam Integer id) throws AppBusinessException{
        if (type.equals("appBusiness")) {
            throw new AppBusinessException("业务异常");
        }
        // 如果我们接口就没有 ?type= 会怎么样呢
        return ResultUtil.success("成功");
    }
  1. 没有传参 ,统一拦截MissingServletRequestParameterException

2.捕获业务的异常

  1. 正常情况
相关推荐
韩立学长6 小时前
基于Springboot酒店管理系统的设计与实现c12044zy(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
数据库·spring boot·后端
liu****6 小时前
一.脚手架介绍以及部分工具使用
开发语言·数据结构·c++·手脚架开发
资深web全栈开发6 小时前
深入理解 Google Wire:Go 语言的编译时依赖注入框架
开发语言·后端·golang
忘记9266 小时前
什么是spring boot
java·spring boot·后端
ohoy6 小时前
EasyPoi 数据脱敏
开发语言·python·excel
Hello World呀6 小时前
Java实现手机号和身份证号脱敏工具类
java·开发语言
曹牧6 小时前
Java:serialVersionUID
java·开发语言
herinspace6 小时前
管家婆软件年结存后快马商城操作注意事项
服务器·数据库·windows
ekprada7 小时前
DAY36 复习日
开发语言·python·机器学习