摘要: SpringBoot異常處理。javascript
在 Web
開發中, 咱們常常會須要處理各類異常, 這是一件棘手的事情, 對於不少人來講, 可能對異常處理有如下幾個問題:前端
try-catch
)異常, 何時須要拋出(throws
)異常到上層.dao
層捕獲仍是在 service
捕獲, 仍是在 controller
層捕獲.既然談到異常, 咱們先來講一下異常處理的反例, 也是不少人容易犯的錯誤, 這裏咱們同時講到前端處理和後端處理 :java
前端代碼git
$.ajax({ type: "GET", url: "/user/add", dataType: "json", success: function(data){ alert("添加成功"); } });
後端代碼github
try { // do something } catch (Exception e) { e.printStackTrace(); }
這是見過最多的異常處理方式了, 若是這是一個添加商品的方法, 前臺經過 ajax 發送請求到後端, 指望返回 json 信息表示添加結果. 但若是這段代碼出現了異常:ajax
error: function(data) {alert("添加失敗");}
) 又如何呢? 到底由於啥失敗了呢, 用戶也不得而知.e.printStackTrace()
打印在控制檯的日誌也會在漫漫的日誌中被埋沒, 極可能會看不到輸出的異常. 但這並非最糟的狀況, 更糟糕的事情是連 e.printStackTrace()
都沒有, catch
塊中是空的, 這樣後端的控制檯中更是什麼都看不到了, 這段代碼會像一個隱形的炸彈同樣一直埋伏在系統中.前端代碼spring
$.ajax({ type: "GET", url: "/goods/add", dataType: "json", success: function(data) { if (data.flag) { alert("添加成功"); } else { alert(data.message); } }, error: function(data){ alert("添加失敗"); } });
後端代碼json
@RequestMapping("/goods/add") @ResponseBody public Map add(Goods goods) { Map map = new HashMap(); try { // do something map.put(flag, true); } catch (Exception e) { e.printStackTrace(); map.put("flag", false); map.put("message", e.getMessage()); } reutrn map; }
這種方式捕獲異常後, 返回了錯誤信息, 且前臺作了必定的處理, 看起來很完善? 但用 HashMap
中的 flag
和 message
這種字符串來當鍵很容易處理, 例如你這裏叫 message
, 別人起名叫 msg
, 甚至有時手抖打錯了, 怎麼辦? 前臺再改爲 msg
或其餘的字符?, 前端後端這樣一直來回改?後端
更有甚者在狀況 A 的狀況下, 返回 json, 在狀況 B 的狀況下, 重定向到某個頁面, 這就更亂了. 對於這種不統一的結構處理起來很是麻煩.springboot
既然要進行統一異常處理, 那麼確定要有一個規範, 不能亂來. 這個規範包含前端和後端.
對的, 不要在業務代碼中進行捕獲異常, 即 dao、service、controller 層的因此異常都所有拋出到上層. 這樣不會致使業務代碼中的一堆 try-catch
會混亂業務代碼.
不要使用 Map 來返回結果, Map 不易控制且容易犯錯, 應該定義一個 Java 實體類. 來表示統一結果來返回, 如定義實體類:
public class ResultBean<T> { private int code; private String message; private Collection<T> data; private ResultBean() { } public static ResultBean error(int code, String message) { ResultBean resultBean = new ResultBean(); resultBean.setCode(code); resultBean.setMessage(message); return resultBean; } public static ResultBean success() { ResultBean resultBean = new ResultBean(); resultBean.setCode(0); resultBean.setMessage("success"); return resultBean; } public static <V> ResultBean<V> success(Collection<V> data) { ResultBean resultBean = new ResultBean(); resultBean.setCode(0); resultBean.setMessage("success"); resultBean.setData(data); return resultBean; } // getter / setter 略 }
ResultBean.success()
或 ResultBean.success(Collection<V> data)
, 不須要返回數據, 即調用前者, 須要返回數據, 調用後者. 如:@RequestMapping("/goods/add") @ResponseBody public ResultBean<Goods> getAllGoods() { List<Goods> goods = goodsService.findAll(); return ResultBean.success(goods); } @RequestMapping("/goods/update") @ResponseBody public ResultBean updateGoods(Goods goods) { goodsService.update(goods); return ResultBean.success(); }
通常只有查詢方法須要調用 ResultBean.success(Collection<V> data)
來返回 N 條數據, 其餘諸如刪除, 修改等方法都應該調用 ResultBean.success()
, 即在業務代碼中只處理正確的功能, 不對異常作任何判斷. 也不須要對 update 或 delete 的更新條數作判斷(我的建議, 實際須要根據業務). 只要沒有拋出異常, 咱們就認爲用戶操做成功了. 且操做成功的提示信息在前端處理, 不要後臺返回 「操做成功」 等字段.
前臺接受到的信息爲:
{ "code": 0, "message": "success", "data": [ { "name": "商品1", "price": 50.00, }, { "name": "商品2", "price": 99.99, } ] }
ResultBean.error(int code, String message)
, 來將狀態碼和錯誤信息返回, 咱們約定 code
爲 0 表示操做成功, 1
或 2
等正數表示用戶輸入錯誤, -1
, -2
等負數表示系統錯誤.前臺接受到的信息爲:
{ "code": -1, "message": "XXX 參數有問題, 請從新填寫", "data": null }
返回的結果集規範後, 前端就很好處理了:
/** * 顯示錯誤信息 * @param result: 錯誤信息 */ function showError(s) { alert(s); } /** * 處理 ajax 請求結果 * @param result: ajax 返回的結果 * @param fn: 成功的處理函數 ( 傳入data: fn(result.data) ) */ function handlerResult(result, fn) { // 成功執行操做,失敗提示緣由 if (result.code == 0) { fn(result.data); } // 用戶操做異常, 這裏能夠對 1 或 2 等錯誤碼進行單獨處理, 也能夠 result.code > 0 來粗粒度的處理, 根據業務而定. else if (result.code == 1) { showError(result.message); } // 系統異常, 這裏能夠對 -1 或 -2 等錯誤碼進行單獨處理, 也能夠 result.code > 0 來粗粒度的處理, 根據業務而定. else if (result.code == -1) { showError(result.message); } // 若是進行細粒度的狀態碼判斷, 那麼就應該重點注意這裏沒出現過的狀態碼. 這個判斷僅建議在開發階段保留用來發現未定義的狀態碼. else { showError("出現未定義的狀態碼:" + result.code); } } /** * 根據 id 刪除商品 */ function deleteGoods(id) { $.ajax({ type: "GET", url: "/goods/delete", dataType: "json", success: function(result){ handlerResult(result, deleteDone); } }); } function deleteDone(data) { alert("刪除成功"); }
showError
和 handlerResult
是公共方法, 分別用來顯示錯誤和統一處理結果集.
而後將主要精力放在發送請求和處理正確結果的方法上便可, 如這裏的 deleteDone 函數, 用來處理操做成功給用戶的提示信息, 正所謂各司其職, 前端負責操做成功的消息提示更合理, 而錯誤信息只有後臺知道, 因此須要後臺來返回.
說了這麼多, 還沒講到後端不在業務層捕獲任何異常的事, 既然全部業務層都沒有捕獲異常, 那麼全部的異常都會拋出到 Controller 層, 咱們只須要用 AOP 對 Controller 層的全部方法處理便可.
好在 Spring 爲咱們提供了一個註解, 用來統一處理異常:
@ControllerAdvice @ResponseBody public class WebExceptionHandler { private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class); @ExceptionHandler public ResultBean unknownAccount(UnknownAccountException e) { log.error("帳號不存在", e); return ResultBean.error(1, "帳號不存在"); } @ExceptionHandler public ResultBean incorrectCredentials(IncorrectCredentialsException e) { log.error("密碼錯誤", e); return ResultBean.error(-2, "密碼錯誤"); } @ExceptionHandler public ResultBean unknownException(Exception e) { log.error("發生了未知異常", e); // 發送郵件通知技術人員. return ResultBean.error(-99, "系統出現錯誤, 請聯繫網站管理員!"); } }
在這裏統一配置須要處理的異常, 一樣, 對於未知的異常, 必定要及時發現, 並進行處理. 推薦出現未知異常後發送郵件, 提示技術人員.
總結一下統一異常處理的方法:
@ControllerAdvice
來處理.一個簡單的演示項目: https://github.com/zhaojun1998/exception-handler-demo
本文做者: 趙俊
本文連接: http://www.zhaojun.im/springboot-exception/
版權聲明: 本博客全部文章除特別聲明外,均採用 BY-NC-SA許可協議。轉載請註明出處!