在互聯網時代,咱們所開發的應用大可能是直面用戶的,程序中的任何一點小疏忽均可能致使用戶的流失,而程序出現異常每每又是不可避免的,那該如何減小程序異常對用戶體驗的影響呢?其實方法很簡單,對異常進行捕獲,而後給予相應的處理便可。但實現的方式卻有好多種,例如:前端
try { ... } catch (Exception e) { doSomeThing(); }
像這種標準的 try-catch 是能夠解決問題,但若是讓你在每一個接口實現裏面都 try-catch 一下,我想你應該是不太願意的。那麼下面來介紹下 SpringBoot 爲咱們提供的處理方式。java
首先,咱們來模擬一下,出現異常的場景,方式比較簡單,直接在正常的代碼裏面拋出一個異常便可。git
在上面的示例中,調用接口時,出現了異常,但客戶端卻收到一個相對正常的響應,這是由於 SpringBoot 默認提供了一個 /error 的映射,該映射被註冊爲 Servlet 容器中的一個全局錯誤頁面用來合理處理全部的異常狀況。但示例中的響應報文不符合咱們定義的數據規範,想要使其知足本身的數據規範,能夠本身定義一個新的 ErrorController,代碼以下:github
@Controller @RequestMapping("${server.error.path:${error.path:/error}}") public class FundaErrorController implements ErrorController { @Override public String getErrorPath() { return "/error"; } @RequestMapping @ResponseBody public Result doHandleError() { return new Result(ResultCode.WEAK_NET_WORK); } }
{ "code": -1, "msg": "網絡異常,請稍後重試", "data": null }
熟悉 SpringMVC 的人應該都知道 @ExceptionHandler 這個註解,在 SpringBoot 裏面,咱們一樣可使用它來作異常捕獲。app
這種方式使用場景較少,但做爲學習 @ExceptionHandler 入門示例仍是很是不錯的,直接在對應的 Controller 裏面增長一個異常處理的方法,並使用 @ExceptionHandler 標識它便可。ide
@ExceptionHandler(Exception.class) public Result handleException() { return new Result(ResultCode.WEAK_NET_WORK); }
客戶端獲得的效果與使用 ErrorController 徹底一致,但對於服務端來講卻不太同樣,若是仔細觀察這兩種方式的日誌輸出的話,會發現使用 ErrorController 時,後臺會打印出異常堆棧信息,而使用 @ExceptionHandler 卻不會,這是由於這兩種處理方式的流程存在着本質的差異。學習
ErrorController: 調用 UserController 拋出異常時,自身沒有作任何處理,因此會打印出堆棧信息,但這個異常會被 Servlet 容器捕捉到,Servlet 容器再將請求轉發給註冊好的異常處理映射 /error 作處理,客戶端收到的實際是 ErrorController 的處理結果,而不是 UserController 的。ui
ExceptionHandler: 異常的處理方法直接被定義在 UserController 裏面,也就是說,在異常拋出的時候,UserController 會使用本身的方法去作異常處理,而不會拋出給 Servlet 容器,因此這個地方沒有打印堆棧信息。.net
若是想要在後臺添加堆棧信息的輸出也很是簡單,只須要將該異常做爲一個參數傳遞給異常處理方法,而後在處理方法裏面作相應的操做便可。
@ExceptionHandler(Exception.class) public Result handleException(Exception e) { e.printStackTrace(); return new Result(ResultCode.WEAK_NET_WORK); }
項目的每每存在着多個 Controller,而它們在異常處理方面有存在着不少的共性,這樣就不太適合在每個 Controller 裏面都編寫一個對應的異常處理方法。能夠將異常處理方法向上挪移到父類中,而後全部的 Controller 統一繼承父類便可。
定義父類 BaseController:
public class BaseController { @ExceptionHandler(Exception.class) public Result handleException(Exception e) { e.printStackTrace(); return new Result(ResultCode.WEAK_NET_WORK); } }
UserController 經過繼承 BaseController 完成異常處理:
@RestController @RequestMapping("/sys/user") public class UserController extends BaseController { ... }
對於使用父級 Controller 完成異常處理也有着它本身的缺點,那就是代碼耦合嚴重,一旦哪天忘記繼承 BaseController,異常又會直達客戶了。想要解除這種耦合關係,可使用 @ControllerAdvice 來協助處理。
@ControllerAdvice @ResponseBody public class ExceptionHandlerAdvice { @ExceptionHandler(Exception.class) public Result handleException(Exception e) { e.printStackTrace(); return new Result(ResultCode.WEAK_NET_WORK); } }
實際的開發場景中,異常是區分不少類別的,不一樣類別的異常須要給用戶不一樣的反饋。例如,在 SpringBoot實戰 之 數據交互篇 中有使用到註解式參數校驗,但校驗不經過緣由並無以有效的方式告之給前端應用。下面咱們經過上面提到的異常處理方式來完成這個功能:
首先,在 ResultCode 類中定義好 參數錯誤 的 code,代碼以下:
PARAMETER_ERROR(10101, "參數錯誤")
在 ExceptionHandlerAdvice 中添加對應的異常處理方法:
@ExceptionHandler(MethodArgumentNotValidException.class) public Result handleIllegalParamException(MethodArgumentNotValidException e) { List<ObjectError> errors = e.getBindingResult().getAllErrors(); String tips = "參數不合法"; if (errors.size() > 0) { tips = errors.get(0).getDefaultMessage(); } Result result = new Result(ResultCode.PARAMETER_ERROR); result.setMsg(tips); return result; }
當應用程序拋出 MethodArgumentNotValidException 時,會精確匹配到該方法,在方法裏面會獲取到校驗結果,並將全部校驗錯誤中的第一條返回給前端應用。
這樣的話,就能夠在 ExceptionHandlerAdvice 裏面添加各類各樣的異常處理方法,以適合不一樣的應用場景。
項目的 github 地址:https://github.com/qchery/funda
原文連接:http://blog.csdn.net/chinrui/article/details/71036544