前言
上一篇文章說到,參數校驗,往往需要和全局的異常攔截器來配套使用,使得返回的數據結構永遠是保持一致的。參數異常springboot默認的返回結構:
{ "timestamp": "2019-04-25T13:09:02.196+0000", "status": 400, "error": "Bad Request", "errors": [ { "codes": [ "Pattern.param.birthday", "Pattern.birthday", "Pattern.java.lang.String", "Pattern" ], "arguments": [ { "codes": [ "param.birthday", "birthday" ], "arguments": null, "defaultMessage": "birthday", "code": "birthday" }, [], { "defaultMessage": "\d{4}-\d{2}-\d{2}", "codes": [ "\d{4}-\d{2}-\d{2}" ], "arguments": null } ], "defaultMessage": "需要匹配正則表達式"\d{4}-\d{2}-\d{2}"", "objectName": "param", "field": "birthday", "rejectedValue": "apple", "bindingFailure": false, "code": "Pattern" } ], "message": "Validation failed for object='param'. Error count: 1", "path": "/validate/notblank" }
不管是正常的情況,還是異常的情況,對於前端(或者app)來說,最好返回值的結構都是一致的,這樣才方便解釋。
定義一個BaseResult類,定義返回值的數據結構
public class BaseResult { private int code; private String message; private Object data; // 省略getter setter方法,全參構造方法 }
不管什么接口,都采用這樣的數據結構返回給前端。比如約定code為0時是成功,其他錯誤定義出具體的錯誤碼,message放錯誤信息,data對象放相應的數據。
定義全局異常處理器GlobalExceptionHandlerAdvice
@RestControllerAdvice public class GlobalExceptionHandlerAdvice { }
使用RestControllerAdvice可以標識一個類為異常捕獲類。
捕獲異常
通過參數異常的測試,可以知道參數有異常時會拋出org.springframework.web.bind.MethodArgumentNotValidException。我們現在手動捕獲 這個異常,並且返回一個BaseResult格式的響應。
@ExceptionHandler(MethodArgumentNotValidException.class) public BaseResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { BindingResult result = e.getBindingResult(); FieldError fieldError = result.getFieldError(); String defaultMessage = fieldError.getDefaultMessage(); return new BaseResult(11000, defaultMessage, null); }
使用ExceptionHandler可以捕獲異常類型。這里捕獲了參數錯誤會拋出的異常,然后返回了自定義的結果。這里錯誤碼為隨便填寫,真實開發,建議定義一個錯誤碼枚舉類。
效果如下:
返回的結果就比較友好了,前端處理起來也方便。
異常流處理業務邏輯
使用異常來處理業務邏輯,會使代碼寫起來更加流暢。比如說,一個刪除用戶數據的方法,返回值為void(無返回值),但是當傳入的用戶id不存在的時候,就應該返回一個用戶不存在的結果,這對於void返回值的方法來說,顯得無能為力。但是,使用異常流來處理該業務邏輯,會變得非常簡單。我們直接拋出一個自定義異常,然后在異常捕獲器上捕獲該異常,再把結果返回給前端即可。
定義一個WebException
public class WebException extends RuntimeException { private int code; private String errorMsg; public WebException(int code, String errorMsg) { super(errorMsg); this.code = code; this.errorMsg = errorMsg; } @Override public synchronized Throwable fillInStackTrace() { return this; } // 省略getter setter方法 }
這里定義了一個運行時異常,重寫了fillInStackTrace方法,重寫該方法是不保留異常信息棧。因為我們使用該異常來處理業務邏輯,都是我們手動拋出的,所以也不需要保存異常信息棧了,這會提升性能。
在異常捕獲器添加WebException異常捕獲
@ExceptionHandler(WebException.class) public BaseResult handleWebException(WebException e) { return new BaseResult(e.getCode(), e.getErrorMsg(), null); }
模擬一段業務邏輯,拋出WebException
在之前的UserController類,修改之前寫的deleteUser方法,如下:
@DeleteMapping(value = "/{userId}")
public Object deleteUser(@PathVariable(value = "userId") Integer userId) {
if (userId == 0) {
throw new WebException(-1, "用戶不存在");
}
return new BaseResult(1, "成功", null);
}
這里定義一個delete請求的接口,接收一個userId參數,如果userId等於0,則返回該用戶不存在。測試結果如下:
當userId為0時,提示用戶不存在
當userId為1時,提示成功.
總結
這里實現了全局異常捕獲,並且介紹了異常流處理業務邏輯。這里只是一個小demo,還有很多待改進的地方。比如說,我沒有定義一個錯誤碼枚舉類。在定義了錯誤碼枚舉類的前提下,修改構造BaseResult的模式,可以采用靜態工廠模式來構造等。這里就不展開討論了
