欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Spring MVC/Boot 統(tǒng)一異常處理最佳實(shí)踐

 更新時(shí)間:2019年01月03日 15:09:04   作者:趙俊  
在 Web 開發(fā)中, 我們經(jīng)常會(huì)需要處理各種異常,這篇文章主要介紹了詳解Spring MVC/Boot 統(tǒng)一異常處理最佳實(shí)踐,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

前言

在 Web 開發(fā)中, 我們經(jīng)常會(huì)需要處理各種異常, 這是一件棘手的事情, 對(duì)于很多人來說, 可能對(duì)異常處理有以下幾個(gè)問題:

  • 什么時(shí)候需要捕獲(try-catch)異常, 什么時(shí)候需要拋出(throws)異常到上層.
  • 在 dao 層捕獲還是在 service 捕獲, 還是在 controller 層捕獲.
  • 拋出異常后要怎么處理. 怎么返回給頁(yè)面錯(cuò)誤信息.

異常處理反例

既然談到異常, 我們先來說一下異常處理的反例, 也是很多人容易犯的錯(cuò)誤, 這里我們同時(shí)講到前端處理和后端處理 :

捕獲異常后只輸出到控制臺(tái)

前端代碼

$.ajax({
  type: "GET",
  url: "/user/add",
  dataType: "json",
  success: function(data){
    alert("添加成功");
  }
});

后端代碼

try {
  // do something
} catch (Exception e) {
  e.printStackTrace();
}

這是見過最多的異常處理方式了, 如果這是一個(gè)添加商品的方法, 前臺(tái)通過 ajax 發(fā)送請(qǐng)求到后端, 期望返回 json 信息表示添加結(jié)果. 但如果這段代碼出現(xiàn)了異常:

  • 那么用戶看到的場(chǎng)景就是點(diǎn)擊了添加按鈕, 但沒有任何反應(yīng)(其實(shí)是返回了 500 錯(cuò)誤頁(yè)面, 但這里前端沒有監(jiān)聽 error 事件, 只監(jiān)聽了 success 事件. 但即使加上了error: function(data) {alert("添加失敗");}) 又如何呢? 到底因?yàn)樯妒×四? 用戶也不得而知.
  • 后臺(tái) e.printStackTrace() 打印在控制臺(tái)的日志也會(huì)在漫漫的日志中被埋沒, 很可能會(huì)看不到輸出的異常. 但這并不是最糟的情況, 更糟糕的事情是連 e.printStackTrace() 都沒有, catch 塊中是空的, 這樣后端的控制臺(tái)中更是什么都看不到了, 這段代碼會(huì)像一個(gè)隱形的炸彈一樣一直埋伏在系統(tǒng)中.

混亂的返回方式

前端代碼

$.ajax({
  type: "GET",
  url: "/goods/add",
  dataType: "json",
  success: function(data) {
    if (data.flag) {
      alert("添加成功");
    } else {
      alert(data.message);
    }
  },
  error: function(data){
    alert("添加失敗");
  }
});

后端代碼

@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;
}

這種方式捕獲異常后, 返回了錯(cuò)誤信息, 且前臺(tái)做了一定的處理, 看起來很完善? 但用 HashMap 中的 flag 和 message 這種字符串來當(dāng)鍵很容易處理, 例如你這里叫 message, 別人起名叫 msg, 甚至有時(shí)手抖打錯(cuò)了, 怎么辦? 前臺(tái)再改成 msg 或其他的字符?, 前端后端這樣一直來回改?

更有甚者在情況 A 的情況下, 返回 json, 在情況 B 的情況下, 重定向到某個(gè)頁(yè)面, 這就更亂了. 對(duì)于這種不統(tǒng)一的結(jié)構(gòu)處理起來非常麻煩.

異常處理規(guī)范

既然要進(jìn)行統(tǒng)一異常處理, 那么肯定要有一個(gè)規(guī)范, 不能亂來. 這個(gè)規(guī)范包含前端和后端.

不要捕獲任何異常

對(duì)的, 不要在業(yè)務(wù)代碼中進(jìn)行捕獲異常, 即 dao、service、controller 層的所以異常都全部拋出到上層. 這樣不會(huì)導(dǎo)致業(yè)務(wù)代碼中的一堆 try-catch 會(huì)混亂業(yè)務(wù)代碼.

統(tǒng)一返回結(jié)果集

不要使用 Map 來返回結(jié)果, Map 不易控制且容易犯錯(cuò), 應(yīng)該定義一個(gè) Java 實(shí)體類. 來表示統(tǒng)一結(jié)果來返回, 如定義實(shí)體類:

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 略
}

正常情況: 調(diào)用 ResultBean.success() 或 ResultBean.success(Collection<V> data), 不需要返回?cái)?shù)據(jù), 即調(diào)用前者, 需要返回?cái)?shù)據(jù), 調(diào)用后者. 如:

@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();
}

一般只有查詢方法需要調(diào)用 ResultBean.success(Collection<V> data) 來返回 N 條數(shù)據(jù), 其他諸如刪除, 修改等方法都應(yīng)該調(diào)用 ResultBean.success(), 即在業(yè)務(wù)代碼中只處理正確的功能, 不對(duì)異常做任何判斷. 也不需要對(duì) update 或 delete 的更新條數(shù)做判斷(個(gè)人建議, 實(shí)際需要根據(jù)業(yè)務(wù)). 只要沒有拋出異常, 我們就認(rèn)為用戶操作成功了. 且操作成功的提示信息在前端處理, 不要后臺(tái)返回 “操作成功” 等字段.

前臺(tái)接受到的信息為:

{
  "code": 0,
  "message": "success",
  "data": [
    {
      "name": "商品1",
      "price": 50.00,
    },
    {
      "name": "商品2",
      "price": 99.99,
    }
  ]
}

拋出異常: 拋出異常后, 我們應(yīng)該調(diào)用 ResultBean.error(int code, String message), 來將狀態(tài)碼和錯(cuò)誤信息返回, 我們約定 code 為 0 表示操作成功, 1 或 2 等正數(shù)表示用戶輸入錯(cuò)誤, -1, -2 等負(fù)數(shù)表示系統(tǒng)錯(cuò)誤.
前臺(tái)接受到的信息為:

{
  "code": -1,
  "message": "XXX 參數(shù)有問題, 請(qǐng)重新填寫",
  "data": null
}

前端統(tǒng)一處理:

返回的結(jié)果集規(guī)范后, 前端就很好處理了:

/**
 * 顯示錯(cuò)誤信息
 * @param result: 錯(cuò)誤信息
 */
function showError(s) {
  alert(s);
}

/**
 * 處理 ajax 請(qǐng)求結(jié)果
 * @param result: ajax 返回的結(jié)果
 * @param fn: 成功的處理函數(shù) ( 傳入data: fn(result.data) )
 */
function handlerResult(result, fn) {
  // 成功執(zhí)行操作,失敗提示原因
  if (result.code == 0) {
    fn(result.data);
  }
  // 用戶操作異常, 這里可以對(duì) 1 或 2 等錯(cuò)誤碼進(jìn)行單獨(dú)處理, 也可以 result.code > 0 來粗粒度的處理, 根據(jù)業(yè)務(wù)而定.
  else if (result.code == 1) {
    showError(result.message);
  }
  // 系統(tǒng)異常, 這里可以對(duì) -1 或 -2 等錯(cuò)誤碼進(jìn)行單獨(dú)處理, 也可以 result.code > 0 來粗粒度的處理, 根據(jù)業(yè)務(wù)而定.
  else if (result.code == -1) {
    showError(result.message);
  }
  // 如果進(jìn)行細(xì)粒度的狀態(tài)碼判斷, 那么就應(yīng)該重點(diǎn)注意這里沒出現(xiàn)過的狀態(tài)碼. 這個(gè)判斷僅建議在開發(fā)階段保留用來發(fā)現(xiàn)未定義的狀態(tài)碼.
  else {
    showError("出現(xiàn)未定義的狀態(tài)碼:" + result.code);
  }
}

/**
 * 根據(jù) id 刪除商品
 */
function deleteGoods(id) {
  $.ajax({
    type: "GET",
    url: "/goods/delete",
    dataType: "json",
    success: function(result){
      handlerResult(result, deleteDone);
    }
  });
}

function deleteDone(data) {
  alert("刪除成功");
}

showError handlerResult 是公共方法, 分別用來顯示錯(cuò)誤和統(tǒng)一處理結(jié)果集.

然后將主要精力放在發(fā)送請(qǐng)求和處理正確結(jié)果的方法上即可, 如這里的 deleteDone 函數(shù), 用來處理操作成功給用戶的提示信息, 正所謂各司其職, 前端負(fù)責(zé)操作成功的消息提示更合理, 而錯(cuò)誤信息只有后臺(tái)知道, 所以需要后臺(tái)來返回.

后端統(tǒng)一處理異常

說了這么多, 還沒講到后端不在業(yè)務(wù)層捕獲任何異常的事, 既然所有業(yè)務(wù)層都沒有捕獲異常, 那么所有的異常都會(huì)拋出到 Controller 層, 我們只需要用 AOP 對(duì) Controller 層的所有方法處理即可.

好在 Spring 為我們提供了一個(gè)注解, 用來統(tǒng)一處理異常:

@ControllerAdvice
@ResponseBody
public class WebExceptionHandler {

  private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);

  @ExceptionHandler
  public ResultBean unknownAccount(UnknownAccountException e) {
    log.error("賬號(hào)不存在", e);
    return ResultBean.error(1, "賬號(hào)不存在");
  }

  @ExceptionHandler
  public ResultBean incorrectCredentials(IncorrectCredentialsException e) {
    log.error("密碼錯(cuò)誤", e);
    return ResultBean.error(-2, "密碼錯(cuò)誤");
  }

  @ExceptionHandler
  public ResultBean unknownException(Exception e) {
    log.error("發(fā)生了未知異常", e);
    // 發(fā)送郵件通知技術(shù)人員.
    return ResultBean.error(-99, "系統(tǒng)出現(xiàn)錯(cuò)誤, 請(qǐng)聯(lián)系網(wǎng)站管理員!");
  }
}

在這里統(tǒng)一配置需要處理的異常, 同樣, 對(duì)于未知的異常, 一定要及時(shí)發(fā)現(xiàn), 并進(jìn)行處理. 推薦出現(xiàn)未知異常后發(fā)送郵件, 提示技術(shù)人員.

總結(jié)

總結(jié)一下統(tǒng)一異常處理的方法:

  1. 不使用隨意返回各種數(shù)據(jù)類型, 要統(tǒng)一返回值規(guī)范.
  2. 不在業(yè)務(wù)代碼中捕獲任何異常, 全部交由 @ControllerAdvice 來處理.

一個(gè)簡(jiǎn)單的演示項(xiàng)目: https://github.com/zhaojun1998/exception-handler-demo

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java基于正則表達(dá)式實(shí)現(xiàn)的替換匹配文本功能【經(jīng)典實(shí)例】

    Java基于正則表達(dá)式實(shí)現(xiàn)的替換匹配文本功能【經(jīng)典實(shí)例】

    這篇文章主要介紹了Java基于正則表達(dá)式實(shí)現(xiàn)的替換匹配文本功能,結(jié)合完整實(shí)例形式分析了java字符串正則替換操作技巧,需要的朋友可以參考下
    2017-04-04
  • 舉例說明JAVA調(diào)用第三方接口的GET/POST/PUT請(qǐng)求方式

    舉例說明JAVA調(diào)用第三方接口的GET/POST/PUT請(qǐng)求方式

    在日常工作和學(xué)習(xí)中,有很多地方都需要發(fā)送請(qǐng)求,這篇文章主要給大家介紹了關(guān)于JAVA調(diào)用第三方接口的GET/POST/PUT請(qǐng)求方式的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Java中instanceof關(guān)鍵字的用法總結(jié)

    Java中instanceof關(guān)鍵字的用法總結(jié)

    instanceof是Java的一個(gè)二元操作符,和==,>,<是同一類東東。由于它是由字母組成的,所以也是Java的保留關(guān)鍵字。它的作用是測(cè)試它左邊的對(duì)象是否是它右邊的類的實(shí)例,返回boolean類型的數(shù)據(jù)
    2013-10-10
  • java實(shí)現(xiàn)簡(jiǎn)易版簡(jiǎn)易版dubbo

    java實(shí)現(xiàn)簡(jiǎn)易版簡(jiǎn)易版dubbo

    dubbo是阿里開源的rpc框架,目前是apache頂級(jí)開源項(xiàng)目,可以用來構(gòu)建微服務(wù)。本文主要介紹了如何通過java實(shí)現(xiàn)簡(jiǎn)易版的dubbo,感興趣的小伙伴可以了解一下
    2021-11-11
  • 10種java數(shù)組合并的常用方法總結(jié)

    10種java數(shù)組合并的常用方法總結(jié)

    在Java中,合并(或連接)數(shù)組是常見的任務(wù),這篇文章主要為大家整理了10個(gè)java中常用的數(shù)組合并方法,文中的示例代碼簡(jiǎn)潔易懂,需要的小伙伴可以參考下
    2023-12-12
  • java多線程CyclicBarrier的使用案例,讓線程起步走

    java多線程CyclicBarrier的使用案例,讓線程起步走

    這篇文章主要介紹了java多線程CyclicBarrier的使用案例,讓線程起步走!具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • Java流程控制語(yǔ)句最全匯總(下篇)

    Java流程控制語(yǔ)句最全匯總(下篇)

    這篇文章主要介紹了Java流程控制語(yǔ)句最全匯總(下篇),本文章內(nèi)容詳細(xì),通過案例可以更好的理解數(shù)組的相關(guān)知識(shí),本模塊分為了三部分,本次為下篇,需要的朋友可以參考下
    2023-01-01
  • java程序員必須要學(xué)會(huì)的linux命令總結(jié)(推薦)

    java程序員必須要學(xué)會(huì)的linux命令總結(jié)(推薦)

    下面小編就為大家分享一篇java程序員必須要學(xué)會(huì)的linux命令總結(jié)(推薦)。具有很好的參考價(jià)值。希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2017-11-11
  • java實(shí)現(xiàn)構(gòu)造無(wú)限層級(jí)樹形菜單

    java實(shí)現(xiàn)構(gòu)造無(wú)限層級(jí)樹形菜單

    這篇文章主要介紹了java實(shí)現(xiàn)構(gòu)造無(wú)限層級(jí)樹形菜單,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • springboot加載注入bean的幾種方式

    springboot加載注入bean的幾種方式

    本文主要介紹了springboot加載注入bean的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-08-08

最新評(píng)論