軟件開發(fā)公司 > 動態(tài) > 行業(yè)資訊
前言
在 Web 開發(fā)中, 我們經(jīng)常會需要處理各種異常, 這是一件棘手的事情, 對于很多人來說, 可能對異常處理有以下幾個問題:
什么時候需要捕獲(try-catch)異常, 什么時候需要拋出(throws)異常到上層.
在 dao 層捕獲還是在 service 捕獲, 還是在 controller 層捕獲.
拋出異常后要怎么處理. 怎么返回給頁面錯誤信息.
異常處理反例
既然談到異常, 我們先來說一下異常處理的反例, 也是很多人容易犯的錯誤, 這里我們同時講到前端處理和后端處理 :
捕獲異常后只輸出到控制臺
前端代碼
$.ajax({
type: "GET",
url: "/user/add",
dataType: "json",
success: function(data){
alert("添加成功");
}
});
后端代碼
try {
// do something
} catch (Exception e) {
e.printStackTrace();
}
這是見過最多的異常處理方式了, 如果這是一個添加商品的方法, 前臺通過 ajax 發(fā)送請求到后端, 期望返回 json 信息表示添加結(jié)果. 但如果這段代碼出現(xiàn)了異常:
那么用戶看到的場景就是點(diǎn)擊了添加按鈕, 但沒有任何反應(yīng)(其實(shí)是返回了 500 錯誤頁面, 但這里前端沒有監(jiān)聽 error 事件, 只監(jiān)聽了 success 事件. 但即使加上了error: function(data) {alert("添加失敗");}) 又如何呢? 到底因?yàn)樯妒×四? 用戶也不得而知.
后臺 e.printStackTrace() 打印在控制臺的日志也會在漫漫的日志中被埋沒, 很可能會看不到輸出的異常. 但這并不是最糟的情況, 更糟糕的事情是連 e.printStackTrace() 都沒有, catch 塊中是空的, 這樣后端的控制臺中更是什么都看不到了, 這段代碼會像一個隱形的炸彈一樣一直埋伏在系統(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;
}
這種方式捕獲異常后, 返回了錯誤信息, 且前臺做了一定的處理, 看起來很完善? 但用 HashMap 中的 flag 和 message 這種字符串來當(dāng)鍵很容易處理, 例如你這里叫 message, 別人起名叫 msg, 甚至有時手抖打錯了, 怎么辦? 前臺再改成 msg 或其他的字符?, 前端后端這樣一直來回改?
更有甚者在情況 A 的情況下, 返回 json, 在情況 B 的情況下, 重定向到某個頁面, 這就更亂了. 對于這種不統(tǒng)一的結(jié)構(gòu)處理起來非常麻煩.
異常處理規(guī)范
既然要進(jìn)行統(tǒng)一異常處理, 那么肯定要有一個規(guī)范, 不能亂來. 這個規(guī)范包含前端和后端.
不要捕獲任何異常
對的, 不要在業(yè)務(wù)代碼中進(jìn)行捕獲異常, 即 dao、service、controller 層的所以異常都全部拋出到上層. 這樣不會導(dǎo)致業(yè)務(wù)代碼中的一堆 try-catch 會混亂業(yè)務(wù)代碼.
統(tǒng)一返回結(jié)果集
不要使用 Map 來返回結(jié)果, Map 不易控制且容易犯錯, 應(yīng)該定義一個 Java 實(shí)體類. 來表示統(tǒng)一結(jié)果來返回, 如定義實(shí)體類:
public?class?ResultBean
正常情況: 調(diào)用 ResultBean.success() 或 ResultBean.success(Collection
@RequestMapping("/goods/add")
@ResponseBody
public ResultBean
一般只有查詢方法需要調(diào)用 ResultBean.success(Collection
前臺接受到的信息為:
{
"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)碼和錯誤信息返回, 我們約定 code 為 0 表示操作成功, 1 或 2 等正數(shù)表示用戶輸入錯誤, -1, -2 等負(fù)數(shù)表示系統(tǒng)錯誤.
前臺接受到的信息為:
{
"code": -1,
"message": "XXX 參數(shù)有問題, 請重新填寫",
"data": null
}
前端統(tǒng)一處理:
返回的結(jié)果集規(guī)范后, 前端就很好處理了:
/**
* 顯示錯誤信息
* @param result: 錯誤信息
*/
function showError(s) {
alert(s);
}
/**
* 處理 ajax 請求結(jié)果
* @param result: ajax 返回的結(jié)果
* @param fn: 成功的處理函數(shù) ( 傳入data: fn(result.data) )
*/
function handlerResult(result, fn) {
// 成功執(zhí)行操作,失敗提示原因
if (result.code == 0) {
fn(result.data);
}
// 用戶操作異常, 這里可以對 1 或 2 等錯誤碼進(jìn)行單獨(dú)處理, 也可以 result.code > 0 來粗粒度的處理, 根據(jù)業(yè)務(wù)而定.
else if (result.code == 1) {
showError(result.message);
}
// 系統(tǒng)異常, 這里可以對 -1 或 -2 等錯誤碼進(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)碼. 這個判斷僅建議在開發(fā)階段保留用來發(fā)現(xiàn)未定義的狀態(tài)碼.
else {
showError("出現(xiàn)未定義的狀態(tài)碼:" + result.code);
}
}
/**
* 根據(jù) id 刪除商品
*/
function deleteGoods(id) {
$.ajax({
type: "DELETE",
url: "/goods/delete",
dataType: "json",
success: function(result){
handlerResult(result, deleteDone);
}
});
}
function deleteDone(data) {
alert("刪除成功");
}
showError 和 handlerResult 是公共方法, 分別用來顯示錯誤和統(tǒng)一處理結(jié)果集.
然后將主要精力放在發(fā)送請求和處理正確結(jié)果的方法上即可, 如這里的 deleteDone 函數(shù), 用來處理操作成功給用戶的提示信息, 正所謂各司其職, 前端負(fù)責(zé)操作成功的消息提示更合理, 而錯誤信息只有后臺知道, 所以需要后臺來返回.
后端統(tǒng)一處理異常
說了這么多, 還沒講到后端不在業(yè)務(wù)層捕獲任何異常的事, 既然所有業(yè)務(wù)層都沒有捕獲異常, 那么所有的異常都會拋出到 Controller 層, 我們只需要用 AOP 對 Controller 層的所有方法處理即可.
好在 Spring 為我們提供了一個注解, 用來統(tǒng)一處理異常:
@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("發(fā)生了未知異常", e);
// 發(fā)送郵件通知技術(shù)人員.
return ResultBean.error(-99, "系統(tǒng)出現(xiàn)錯誤, 請聯(lián)系網(wǎng)站管理員!");
}
}
在這里統(tǒng)一配置需要處理的異常, 同樣, 對于未知的異常, 一定要及時發(fā)現(xiàn), 并進(jìn)行處理. 推薦出現(xiàn)未知異常后發(fā)送郵件, 提示技術(shù)人員.
總結(jié)
總結(jié)一下統(tǒng)一異常處理的方法:
不使用隨意返回各種數(shù)據(jù)類型, 要統(tǒng)一返回值規(guī)范.
不在業(yè)務(wù)代碼中捕獲任何異常, 全部交由 @ControllerAdvice 來處理.
銳智互動/銳智開高軟件遵循嚴(yán)格的質(zhì)量和安全標(biāo)準(zhǔn), 實(shí)施嚴(yán)密的安全措施, 擁有成熟可靠的管理和開發(fā)流程, 公司憑借多年的行業(yè)積累、深厚的 行業(yè)專長和成熟的行業(yè)實(shí)踐,為客戶持續(xù)創(chuàng)造關(guān)鍵價值。我們始終關(guān) 注前沿技術(shù),保持國際領(lǐng)先的眼界和技術(shù)儲備。公司自 成立以來, 在團(tuán)隊(duì)成員的共同努力下,已經(jīng)成功服務(wù)于上百家企業(yè),其中包括 我愛我家、聯(lián)東集團(tuán)、優(yōu)財(cái)CMA、5100、奔馳、華為、伊利、寶馬、 迪思公關(guān)、航天國旅、HOTWIND、北京電通等眾多知名企業(yè)。
Tag:北京軟件開發(fā)公司 系統(tǒng)開發(fā)
Tag:app開發(fā) ui設(shè)計(jì) 軟件開發(fā)
Tag:app開發(fā)-軟件開發(fā)-微信小程序開發(fā)
Tag:app開發(fā) 軟件開發(fā)外包 軟件開發(fā)公司
Tag:app開發(fā) 軟件開發(fā)公司 軟件定制
Tag:app開發(fā)公司 軟件開發(fā) 建模構(gòu)造
Ruizhi Interactive Network Technology Co. Ltd.
服務(wù)熱線(國外用戶請加0086):
400-1050-360 7×24小時
項(xiàng)目經(jīng)理:QQ:84083083電話/微信:15201301399
項(xiàng)目經(jīng)理:QQ:18818131電話/微信:13520607989
電子郵箱:PMO@irzhd.com
歡迎掃碼關(guān)注、咨詢
北京公司:北京市朝陽區(qū)住邦2000商務(wù)中心1號樓B區(qū)
上海公司:上海市松江區(qū)車墩北松路5255號2樓
成都公司:四川省成都市高新區(qū)益州大道復(fù)城國際T4
2009-2023 銳智互動/銳智開高軟件.All Right Reserved.京ICP備15026839號-1 隱私政策