前言
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、碼雲: