
簡介
日常開發過程中,難免有的程序會因為某些原因拋出異常,而這些異常一般都是利用try ,catch的方式處理異常或者throw,throws的方式拋出異常不管。這種方法對于程序員來說處理也比較麻煩,對客戶來說也不太友好,所以我們希望既能方便程序員編寫代碼,不用過多的自己去處理各種異常編寫重復的代碼又能提升用戶的體驗,這時候全局異常處理就顯得很重要也很便捷了,是一種不錯的選擇。
全網獨家Spring/Cloud/MVC/Boot,腦圖+面試+進階,就問你服不服??zhuanlan.zhihu.com
一、 全局異常捕獲與處理
Springboot對于異常的處理做了不錯的支持,它提供了兩個可用的注解。
@ControllerAdvice:用來開啟全局的異常捕獲
@ExceptionHandler:說明捕獲哪些異常,對哪些異常進行處理。
@ControllerAdvice
public class MyExceptionHandler {@ExceptionHandler(value =Exception.class)public String exceptionHandler(Exception e){System.out.println("發生了一個異常"+e);return e.getMessage();}
}
上面這段代碼的意思是,只要是代碼運行過程中有異常就會進行捕獲,并輸出出這個異常。然后我們隨便編寫一個會發生異常的代碼,測試出來的異常是這樣的。

這對于前后端分離來說并不好,前后端分離之后唯一的交互就是json了,我們也希望將后端的異常變成json返回給前端處理,所以就需要統一結果返回和統一異常處理。
二、統一結果返回與統一異常
Result類:封裝返回結果。
public class Result<T> {private Integer code;//狀態碼private String message;//提示消息private T data;//數據public Result() {}/*** @param code 響應碼* @param message 響應信息*/public Result(Integer code, String message) {this.code = code;this.message = message;}/*** @param code 響應碼* @param message 響應信息* @param data 數據*/public Result(Integer code, String message, T data) {this.code = code;this.message = message;this.data = data;}/*** @param resultEnum 自定義枚舉類,包含 code 和 message*/public Result(ResultEnum resultEnum) {this.code = resultEnum.getCode();this.message = resultEnum.getMessage();}/*** @param resultEnum 自定義枚舉類,包含 code 和 message* @param data 數據*/public Result(ResultEnum resultEnum, T data) {this.code = resultEnum.getCode();this.message = resultEnum.getMessage();this.data = data;}/*** 自定義異常返回的結果* @param definitionException 自定義異常處理類* @return 返回自定義異常*/public static Result<Object> defineError(DefinitionException definitionException) {return new Result<>(definitionException.getErrorCode(), definitionException.getErrorMessage());}/*** 其他異常處理方法返回的結果* @param resultEnum 自定義枚舉類,包含 code 和 message* @return 返回其他異常*/public static Result<Object> otherError(ResultEnum resultEnum) {return new Result<>(resultEnum);}//這里寫get和set方法
}
注意:其中省略了get,set方法。
ResultEnum:自定義枚舉類。
public enum ResultEnum {// 數據操作定義SUCCESS(200, "成功"),TIME_OUT(130, "訪問超時"),NO_PERMISSION(403, "拒絕訪問"),NO_AUTH(401, "未經授權訪問"),NOT_FOUND(404, "無法找到資源"),METHOD_NOT_ALLOWED(405, "不支持當前請求方法"),SERVER_ERROR(500, " 服務器運行異常"),NOT_PARAM(10001, "參數不能為空"),NOT_EXIST_USER_OR_ERROR_PASSWORD(10002, "該用戶不存在或密碼錯誤"),NOT_PARAM_USER_OR_ERROR_PASSWORD(10003, "用戶名或密碼為空");;/*** 響應碼*/private final Integer code;/*** 響應信息*/private final String message;/*** 有參構造* @param code 響應碼* @param message 響應信息*/ResultEnum(Integer code, String message) {this.code = code;this.message = message;}public Integer getCode() {return code;}public String getMessage() {return message;}
}
注意:枚舉類中定義了常見的錯誤碼以及錯誤的提示信息。這里我們就定義好了統一的結果返回,其中里面的靜態方法是用來當程序異常的時候轉換成異常返回規定的格式。
DefinitionException:自定義異常處理類。
//@ControllerAdvice+@ResponseBody,開啟全局的異常捕獲,返回JSON
@RestControllerAdvice
public class GlobalExceptionHandler {/*** 處理自定義異常* @return Result* @ExceptionHandler 說明捕獲哪些異常,對那些異常進行處理。*/@ExceptionHandler(value = DefinitionException.class)public Result<Object> customExceptionHandler(DefinitionException e) {return Result.defineError(e);}/*** 處理其他異常* @return Result*/@ExceptionHandler(value = Exception.class)public Result<Object> exceptionHandler(Exception e) {return Result.otherError(ErrorEnum.INTERNAL_SERVER_ERROR);}
}
說明:將對象解析成json,是為了方便前后端的交互。
三、代碼測試與結果
測試類
ResultController:測試的controller類
@RestController
public class ResultController {//獲取學生信息@GetMapping("/student")public Result<Student> getStudent() {Student student = new Student();student.setId(1);student.setAge(18);student.setName("XuWwei")return new Result<>(ResultEnum.SUCCESS, student);}//自定義異常處理@RequestMapping("/getDeException")public Result<Object> DeException() {throw new DefinitionException(400, "我出錯了");}//其他異常處理@RequestMapping("/getException")public Result Exception(){Result result = new Result();int a=1/0;return result;}
Student:學生類
public class Student {/*** 唯一標識id*/private Integer id;/*** 姓名*/private String name;/*** 年齡*/private Integer age;
}
注意:其中省略了get,set方法。
測試結果
啟動項目,一個一個測試
- 正常測試

可以看到數據是正常返回json,沒有異常。
- 自定義異常

可以看到這個自定義的異常被捕獲到了,并且返回了一個json。
- 其他異常

可以看到這個異常被捕獲到了,并且返回了一個json。
注意:這種方法是不能處理404異常的,捕獲不到。
四、404異常特殊處理
1、修改配置文件
? 默認情況下,SpringBoot是不會拋出404異常的,所以**@ControllerAdvice**也不能捕獲到404異常。我們可以通過配置文件來讓這個注解能捕獲到404異常,在application.properties中添加以下配置:
#當發現404異常時直接拋出異常
spring.mvc.throw-exception-if-no-handler-found=true
#關閉默認的靜態資源路徑映射,這樣404不會跳轉到默認的頁面
spring.resources.add-mappings=false
但是關閉默認的靜態資源路徑映射會讓靜態資源訪問出現問題,也就是不適合前后端一體的情況。
但是我們可以手動配置靜態資源路徑映射,就能正常訪問靜態資源了。
@Configuration
public class ResourceConfig implements WebMvcConfigurer {@Overridepublic void addResourceHandlers(ResourceHandlerRegistry registry) {//可以訪問localhost:8080/static/images/image.jpgregistry.addResourceHandler("/static/**").addResourceLocations("classpath:/static/");}
}
2、修改error跳轉路徑
? 關閉默認的靜態資源路徑映射顯然不太合理,可能會導致其他的錯誤發生,所以也可以通過修改默認錯誤頁面的跳轉路徑來達到我們的目的。
在GlobalExceptionHandler類中添加NotFoundExceptionHandler類,這個類繼承了ErrorController,可以重寫error的跳轉路徑
//處理404NotFoundException
@Controller
class NotFoundExceptionHandler implements ErrorController {//設置錯誤頁面路徑@Overridepublic String getErrorPath() {return "/error";}//當訪問error路徑時,返回一個封裝的異常的Json@RequestMapping("/error")@ResponseBodypublic Result<Object> error() {return Result.otherError(ResultEnum.NOT_FOUND);}
}
五、拓展異常類
? GlobalExceptionHandler的exceptionHandler方法將所有的異常統一返回500系統錯誤,這不符合我們的設想,所以我們可以通過判斷異常的類型,來返回不同的值。
將exceptionHandler改成以下代碼:
/*** 處理其他異常* @return Result*/
@ExceptionHandler(value = Exception.class)
public Result<Object> exceptionHandler(Exception e) {if (e instanceof NullPointerException){//捕獲空指針異常return Result.otherError(ResultEnum.NOT_PARAM);}else if (e instanceof IllegalAccessException){//非法訪問異常return Result.otherError(ResultEnum.NO_PERMISSION);} else{return Result.otherError(ResultEnum.SERVER_ERROR);}
}
注意:更多異常可以通過else if來細分。
六、總結
?springboot的異常處理,需要通過@ControllerAdvice注解以及 @ExceptionHandler注解,來攔截所有的異常,并通過一個封裝返回值返回。但是,這兩個注解無法捕獲404NotFound異常,因為SpringBoot默認是不會拋出404異常的,所以要通過繼承ErrorController來修改404異常的跳轉路徑,達到捕獲404異常的目的。