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


  前言

  springboot內置的/error錯誤頁面並不一定適用我們的項目,這時候就需要進行自定義統一異常處理,本文記錄springboot進行自定義統一異常處理。

  

  1、使用@ControllerAdvice、@RestControllerAdvice捕獲運行時異常。

  2、重寫ErrorController,手動拋出自定義ErrorPageException異常,方便404、403等被統一處理。

 

  官網文檔相關介紹:

  https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#boot-features-error-handling

  

 

  代碼

  項目結構

 

 

  引入我們父類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(String code,String msg) {
        super(code, msg);
    }
}

 

  重寫ErrorController,不在跳轉原生錯誤頁面,而是拋出我們的自定義異常

/**
 * 自定義errorPage
 * 直接繼承 BasicErrorController
 */
@Controller
public class ErrorPageConfig extends BasicErrorController {

    public ErrorPageConfig(){
        super(new DefaultErrorAttributes(),new ErrorProperties());
    }

    @Override
    @RequestMapping(
            produces = {"text/html"}
    )
    public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        doError(request);
        return null;
    }

    @Override
    @RequestMapping
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        doError(request);
        return null;
    }

    private void doError(HttpServletRequest request) {
        Map<String, Object> model = this.getErrorAttributes(request, this.isIncludeStackTrace(request, MediaType.ALL));

        //拋出ErrorPageException異常,方便被ExceptionHandlerConfig處理
        String path = model.get("path").toString();
        String status = model.get("status").toString();

        //靜態資源文件發生404,無需拋出異常
        if(!path.contains("/common/") && !path.contains(".")){
            throw new ErrorPageException(status, path);
        }
    }
}

 

   @RestControllerAdvice,統一異常處理,捕獲並返回統一返回對象Result,同時把異常信息打印到日志中

/**
 * 統一異常處理
 */
@Slf4j
@RestControllerAdvice
public class ExceptionHandlerConfig{

    /**
     * 業務異常 統一處理
     */
    @ExceptionHandler(value = ServiceException.class)
    @ResponseBody
    public Object exceptionHandler400(ServiceException e){
        return returnResult(e,Result.error(e.getErrorEnum()));
    }

    /**
     * 錯誤頁面異常 統一處理
     */
    @ExceptionHandler(value = ErrorPageException.class)
    @ResponseBody
    public Object exceptionHandler(ErrorPageException e){
        ErrorEnum errorEnum;
        switch (Integer.parseInt(e.getCode())) {
            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;
        }

        return returnResult(e,Result.error(errorEnum));
    }

    /**
     * 空指針異常 統一處理
     */
    @ExceptionHandler(value =NullPointerException.class)
    @ResponseBody
    public Object exceptionHandler500(NullPointerException e){
        return returnResult(e,Result.error(ErrorEnum.INTERNAL_SERVER_ERROR));
    }

    /**
     * 其他異常 統一處理
     */
    @ExceptionHandler(value =Exception.class)
    @ResponseBody
    public Object exceptionHandler(Exception e){
        return returnResult(e,Result.of(ErrorEnum.UNKNOWN.getCode(), false, "【" + e.getClass().getName() + "】" + e.getMessage()));
    }

    /**
     * 是否為ajax請求
     * ajax請求,響應json格式數據,否則應該響應html頁面
     */
    private Object returnResult(Exception e,Result errorResult){
        //把錯誤信息輸入到日志中
        log.error(ErrorUtil.errorInfoToString(e));

        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpServletResponse response = requestAttributes.getResponse();

        //設置http響應狀態
        response.setStatus(200);

        //判斷是否為ajax請求
        if ("XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With"))){
            return errorResult;
        }else{
            return new ModelAndView("error","msg",errorResult.getMsg());
        }
    }
}

 

  新建測試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

 

  更新

  2022-03-22更新:統一異常處理返回格式調整:ajax請求返回json格式數據,其他情況下跳轉自定義error頁面

 

 

 

  后記

  自定義統一異常處理暫時先記錄到這,后續再進行補充。

  

  代碼開源

 

  代碼已經開源、托管到我的GitHub、碼雲:

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

  碼雲:https://gitee.com/huanzi-qch/springBoot


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM