SpringBoot系列——自定义统一异常处理

作者: 博客园精华区  更新时间:2021-05-20 11:15:00  原文链接


前言

springboot内置的/error错误页面并不一定适用我们的项目,这时候就需要进行自定义统一异常处理,本文记录springboot进行自定义统一异常处理。

1、使用@ControllerAdvice、@RestControllerAdvice捕获运行时异常。

2、重写ErrorController,手动抛出自定义ErrorPageException异常,方便404、403等被统一处理。

代码

项目结构

引入我们父类pom即可,无需引入其他依赖

开始之前,需要先定下统一返回对象、自定义异常枚举类

/**
 * 自定义异常枚举类
 */
public enum ErrorEnum {
    //自定义系列
    USER_NAME_IS_NOT_NULL("10001","【参数校验】用户名不能为空"),
    PWD_IS_NOT_NULL("10002","【参数校验】密码不能为空"),

    //400系列
    BAD_REQUEST("400","请求的数据格式不符!"),
    UNAUTHORIZED("401","登录凭证过期!"),
    FORBIDDEN("403","抱歉,你无权限访问!"),
    NOT_FOUND("404", "请求的资源找不到!"),

    //500系列
    INTERNAL_SERVER_ERROR("500", "服务器内部错误!"),
    SERVICE_UNAVAILABLE("503","服务器正忙,请稍后再试!"),

    //未知异常
    UNKNOWN("10000","未知异常!");


    /** 错误码 */
    private String code;

    /** 错误描述 */
    private String msg;

    ErrorEnum(String code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public String getCode() {
        return code;
    }

    public String getMsg() {
        return msg;
    }
}
/**
 * 统一返回对象
 */

@Data
public class Result<T> implements Serializable {
    /**
     * 通信数据
     */
    private T data;
    /**
     * 通信状态
     */
    private boolean flag = true;
    /**
     * 通信描述
     */
    private String msg = "操作成功";

    /**
     * 通过静态方法获取实例
     */
    public static <T> Result<T> of(T data) {
        return new Result<>(data);
    }

    public static <T> Result<T> of(T data, boolean flag) {
        return new Result<>(data, flag);
    }

    public static <T> Result<T> of(T data, boolean flag, String msg) {
        return new Result<>(data, flag, msg);
    }

    public static <T> Result<T> error(ErrorEnum errorEnum) {
        return new Result(errorEnum.getCode(), false, errorEnum.getMsg());
    }

    @Deprecated
    public Result() {

    }

    private Result(T data) {
        this.data = data;
    }

    private Result(T data, boolean flag) {
        this.data = data;
        this.flag = flag;
    }

    private Result(T data, boolean flag, String msg) {
        this.data = data;
        this.flag = flag;
        this.msg = msg;
    }

}

新增两个自定义异常,便于统一处理时捕获异常

/**
 * 自定义业务异常
 */
public class ServiceException extends RuntimeException {

    /**
     * 自定义异常枚举类
     */
    private ErrorEnum errorEnum;

    /**
     * 错误码
     */
    private String code;

    /**
     * 错误信息
     */
    private String errorMsg;


    public ServiceException() {
        super();
    }

    public ServiceException(ErrorEnum errorEnum) {
        super("{code:" + errorEnum.getCode() + ",errorMsg:" + errorEnum.getMsg() + "}");
        this.errorEnum = errorEnum;
        this.code = errorEnum.getCode();
        this.errorMsg = errorEnum.getMsg();
    }

    public ServiceException(String code,String errorMsg) {
        super("{code:" + code + ",errorMsg:" + errorMsg + "}");
        this.code = code;
        this.errorMsg = errorMsg;
    }

    public ErrorEnum getErrorEnum() {
        return errorEnum;
    }

    public String getErrorMsg() {
        return errorMsg;
    }

    public void setErrorMsg(String errorMsg) {
        this.errorMsg = errorMsg;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}
/**
 * 自定义错误页面异常
 */
public class ErrorPageException extends ServiceException {

    public ErrorPageException(ErrorEnum errorEnum) {
        super(errorEnum);
    }
}

重写ErrorController,不在跳转原生错误页面,而是抛出我们的自定义异常

/**
 * 自定义errorPage
 */
@Controller
public class ErrorPageConfig implements ErrorController{

    private final static String ERROR_PATH = "/error" ;

    @Override
    public String getErrorPath() {
        return ERROR_PATH;
    }

    @RequestMapping(ERROR_PATH)
    public void errorPathHandler(HttpServletResponse response) {
        //抛出ErrorPageException异常,方便被ExceptionHandlerConfig处理
        ErrorEnum errorEnum;
        switch (response.getStatus()) {
            case 404:
                errorEnum = ErrorEnum.NOT_FOUND;
                break;
            case 403:
                errorEnum = ErrorEnum.FORBIDDEN;
                break;
            case 401:
                errorEnum = ErrorEnum.UNAUTHORIZED;
                break;
            case 400:
                errorEnum = ErrorEnum.BAD_REQUEST;
                break;
            default:
                errorEnum = ErrorEnum.UNKNOWN;
                break;
        }
        throw new ErrorPageException(errorEnum);
    }
}

@RestControllerAdvice,统一异常处理,捕获并返回统一返回对象Result,同时把异常信息打印到日志中

/**
 * 统一异常处理
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerConfig{

    /**
     * 业务异常 统一处理
     */
    @ExceptionHandler(value = ServiceException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ResponseBody
    public Result exceptionHandler400(ServiceException e){
        //把错误信息输入到日志中
        log.error(ErrorUtil.errorInfoToString(e));
        return Result.error(e.getErrorEnum());
    }

    /**
     * 错误页面异常 统一处理
     */
    @ExceptionHandler(value = ErrorPageException.class)
    @ResponseBody
    public Result exceptionHandler(ErrorPageException e){
        //把错误信息输入到日志中
        log.error(ErrorUtil.errorInfoToString(e));
        return Result.error(e.getErrorEnum());
    }

    /**
     * 空指针异常 统一处理
     */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public Result exceptionHandler500(NullPointerException e){
        //把错误信息输入到日志中
        log.error(ErrorUtil.errorInfoToString(e));
        return Result.error(ErrorEnum.INTERNAL_SERVER_ERROR);
    }

    /**
     * 未知异常 统一处理
     */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public Result exceptionHandler(Exception e){
        //把错误信息输入到日志中
        log.error(ErrorUtil.errorInfoToString(e));
        return Result.error(ErrorEnum.UNKNOWN);
    }
}

新建测试controller,新增几个测试接口,模拟多种异常报错的情况

/**
 * 模拟异常测试
 */
@RestController
@RequestMapping("/test/")
public class TestController {
    /**
     * 正常返回数据
     */
    @GetMapping("index")
    public Result index(){
        return Result.of("正常返回数据");
    }

    /**
     * 模拟空指针异常
     */
    @GetMapping("nullPointerException")
    public Result nullPointerException(){
        //故意制造空指针异常
        String msg = null;
        msg.equals("huanzi-qch");
        return Result.of("正常返回数据");
    }

    /**
     * 模拟业务异常,手动抛出业务异常
     */
    @GetMapping("serviceException")
    public Result serviceException(){
        throw new ServiceException(ErrorEnum.USER_NAME_IS_NOT_NULL);
    }
}

效果

正常数据返回

http://localhost:10010/test/index

模拟空指针异常

http://localhost:10010/test/nullPointerException

模拟业务异常

http://localhost:10010/test/serviceException

调用错误接口,404

http://localhost:10010/test/serviceException111

后记

自定义统一异常处理暂时先记录到这,后续再进行补充。

代码开源

代码已经开源、托管到我的GitHub、码云:

GitHub: https://github.com/huanzi-qch/springBoot

码云: https://gitee.com/huanzi-qch/springBoot