SpringBoot系列——自定義統一異常處理

qch發表於2021-05-20

  前言

  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

相關文章