激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 詳解Spring MVC/Boot 統一異常處理最佳實踐

詳解Spring MVC/Boot 統一異常處理最佳實踐

2021-06-26 13:56趙俊 Java教程

在 Web 開發中, 我們經常會需要處理各種異常,這篇文章主要介紹了詳解Spring MVC/Boot 統一異常處理最佳實踐,具有一定的參考價值,感興趣的小伙伴們可以參考一下

前言

在 web 開發中, 我們經常會需要處理各種異常, 這是一件棘手的事情, 對于很多人來說, 可能對異常處理有以下幾個問題:

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

異常處理反例

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

捕獲異常后只輸出到控制臺

前端代碼

?
1
2
3
4
5
6
7
8
$.ajax({
  type: "get",
  url: "/user/add",
  datatype: "json",
  success: function(data){
    alert("添加成功");
  }
});

后端代碼

?
1
2
3
4
5
try {
  // do something
} catch (exception e) {
  e.printstacktrace();
}

這是見過最多的異常處理方式了, 如果這是一個添加商品的方法, 前臺通過 ajax 發送請求到后端, 期望返回 json 信息表示添加結果. 但如果這段代碼出現了異常:

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

混亂的返回方式

前端代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$.ajax({
  type: "get",
  url: "/goods/add",
  datatype: "json",
  success: function(data) {
    if (data.flag) {
      alert("添加成功");
    } else {
      alert(data.message);
    }
  },
  error: function(data){
    alert("添加失敗");
  }
});

后端代碼

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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 的情況下, 重定向到某個頁面, 這就更亂了. 對于這種不統一的結構處理起來非常麻煩.

異常處理規范

既然要進行統一異常處理, 那么肯定要有一個規范, 不能亂來. 這個規范包含前端和后端.

不要捕獲任何異常

對的, 不要在業務代碼中進行捕獲異常, 即 dao、service、controller 層的所以異常都全部拋出到上層. 這樣不會導致業務代碼中的一堆 try-catch 會混亂業務代碼.

統一返回結果集

不要使用 map 來返回結果, map 不易控制且容易犯錯, 應該定義一個 java 實體類. 來表示統一結果來返回, 如定義實體類:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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), 不需要返回數據, 即調用前者, 需要返回數據, 調用后者. 如:

?
1
2
3
4
5
6
@requestmapping("/goods/add")
@responsebody
public resultbean<goods> getallgoods() {
  list<goods> goods = goodsservice.findall();
  return resultbean.success(goods);
}
?
1
2
3
4
5
6
@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 的更新條數做判斷(個人建議, 實際需要根據業務). 只要沒有拋出異常, 我們就認為用戶操作成功了. 且操作成功的提示信息在前端處理, 不要后臺返回 “操作成功” 等字段.

前臺接受到的信息為:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "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 等負數表示系統錯誤.
前臺接受到的信息為:

?
1
2
3
4
5
{
  "code": -1,
  "message": "xxx 參數有問題, 請重新填寫",
  "data": null
}

前端統一處理:

返回的結果集規范后, 前端就很好處理了:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/**
 * 顯示錯誤信息
 * @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 為我們提供了一個注解, 用來統一處理異常:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@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, "系統出現錯誤, 請聯系網站管理員!");
  }
}

在這里統一配置需要處理的異常, 同樣, 對于未知的異常, 一定要及時發現, 并進行處理. 推薦出現未知異常后發送郵件, 提示技術人員.

總結

總結一下統一異常處理的方法:

  1. 不使用隨意返回各種數據類型, 要統一返回值規范.
  2. 不在業務代碼中捕獲任何異常, 全部交由 @controlleradvice 來處理.

一個簡單的演示項目: https://github.com/zhaojun1998/exception-handler-demo

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。

原文鏈接:http://www.zhaojun.im/springboot-exception/

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 午色影院 | 欧美顶级毛片在线播放小说 | 在线播放免费视频 | 欧美一级网 | 国产成人强伦免费视频网站 | 国产电影av在线 | 噜噜色av| 欧美日韩精品一区二区三区蜜桃 | 欧美性a视频 | 一区二区视频在线看 | 黄色电影免费网址 | 欧美在线小视频 | 9191色| 午夜生活理论片 | 亚洲婷婷日日综合婷婷噜噜噜 | 欧美成人午夜一区二区三区 | av在线播放电影 | 欧美日韩免费在线观看视频 | 日日草日日干 | 女人解衣喂奶电影 | 啪啪激情 | 欧美a在线看 | 99热99精品 | 国产一区二区三区在线免费 | 国产超碰人人做人人爱 | 性盈盈盈影院 | av噜噜在线 | 黄色免费视频观看 | 久久一区二区三区av | chinesehdxxxx实拍 日韩电影视频 | 国外成人在线视频网站 | 日韩中文字幕一区二区三区 | 插插操| 国产在线1区 | 白天操夜夜操 | 精品国产一区二 | 奶子吧naiziba.cc免费午夜片在线观看 | 中文字幕涩涩久久乱小说 | 国产91九色视频 | vidz 98hd | 精品在线观看一区二区 |