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