成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Spring Boot統(tǒng)一異常處理實踐

call_me_R / 1881人閱讀

摘要: SpringBoot異常處理。

原文:Spring MVC/Boot 統(tǒng)一異常處理最佳實踐

作者:趙俊

前言

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)了異常:

那么用戶看到的場景就是點擊了添加按鈕, 但沒有任何反應(yīng)(其實是返回了 500 錯誤頁面, 但這里前端沒有監(jiān)聽 error 事件, 只監(jiān)聽了 success 事件. 但即使加上了error: function(data) {alert("添加失敗");}) 又如何呢? 到底因為啥失敗了呢, 用戶也不得而知.

后臺 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 中的 flagmessage 這種字符串來當鍵很容易處理, 例如你這里叫 message, 別人起名叫 msg, 甚至有時手抖打錯了, 怎么辦? 前臺再改成 msg 或其他的字符?, 前端后端這樣一直來回改?

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

異常處理規(guī)范

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

不要捕獲任何異常

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

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

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

public class ResultBean {
    private int code;
    private String message;
    private Collection 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  ResultBean success(Collection 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 data), 不需要返回數(shù)據(jù), 即調(diào)用前者, 需要返回數(shù)據(jù), 調(diào)用后者. 如:

@RequestMapping("/goods/add")
@ResponseBody
public ResultBean getAllGoods() {
    List 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 data) 來返回 N 條數(shù)據(jù), 其他諸如刪除, 修改等方法都應(yīng)該調(diào)用 ResultBean.success(), 即在業(yè)務(wù)代碼中只處理正確的功能, 不對異常做任何判斷. 也不需要對 update 或 delete 的更新條數(shù)做判斷(個人建議, 實際需要根據(jù)業(yè)務(wù)). 只要沒有拋出異常, 我們就認為用戶操作成功了. 且操作成功的提示信息在前端處理, 不要后臺返回 “操作成功” 等字段.

前臺接受到的信息為:

{
    "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 表示操作成功, 12 等正數(shù)表示用戶輸入錯誤, -1, -2 等負數(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 等錯誤碼進行多帶帶處理, 也可以 result.code > 0 來粗粒度的處理, 根據(jù)業(yè)務(wù)而定.
    else if (result.code == 1) {
        showError(result.message);
    }
    // 系統(tǒng)異常, 這里可以對 -1 或 -2 等錯誤碼進行多帶帶處理, 也可以 result.code > 0 來粗粒度的處理, 根據(jù)業(yè)務(wù)而定.
    else if (result.code == -1) {
        showError(result.message);
    }
    // 如果進行細粒度的狀態(tài)碼判斷, 那么就應(yīng)該重點注意這里沒出現(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: "GET",
        url: "/goods/delete",
        dataType: "json",
        success: function(result){
            handlerResult(result, deleteDone);
        }
    });
}

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

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

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

后端統(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), 并進行處理. 推薦出現(xiàn)未知異常后發(fā)送郵件, 提示技術(shù)人員.

總結(jié)

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

不使用隨意返回各種數(shù)據(jù)類型, 要統(tǒng)一返回值規(guī)范.

不在業(yè)務(wù)代碼中捕獲任何異常, 全部交由 @ControllerAdvice 來處理.

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

本文作者: 趙俊
本文鏈接: http://www.zhaojun.im/springboot-exception/
版權(quán)聲明: 本博客所有文章除特別聲明外,均采用BY-NC-SA許可協(xié)議。轉(zhuǎn)載請注明出處!

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/73040.html

相關(guān)文章

  • Spring Boot & Spring MVC 異常處理的N種方法

    摘要:定制特定異常返回結(jié)果根據(jù)官方文檔的例子,可以使用和對特定異常返回特定的結(jié)果。下面是用瀏覽器和訪問的結(jié)果無輸出注意上方表格的錯誤,產(chǎn)生這個的原因前面已經(jīng)講過。不過需要注意的是,無法通過設(shè)定,由或者容器決定里一律是。 github:https://github.com/chanjarste... 參考文檔: Spring Boot 1.5.4.RELEASE Documentation ...

    h9911 評論0 收藏0
  • Spring Boot 2.x 系列教程:WebFlux REST API 全局異常處理 Error

    摘要:挺多人咨詢的,異常處理用切面注解去實現(xiàn)去全局異常處理。全局異常處理類,代碼如下代碼解析如下抽象類是用來處理全局錯誤時進行擴展和實現(xiàn)注解標記的切面排序,值越小擁有越高的優(yōu)先級,這里設(shè)置優(yōu)先級偏高。 本文內(nèi)容 為什么要全局異常處理? WebFlux REST 全局異常處理實戰(zhàn) 小結(jié) 摘錄:只有不斷培養(yǎng)好習慣,同時不斷打破壞習慣,我們的行為舉止才能夠自始至終都是正確的。 一、為什么要全局...

    BicycleWarrior 評論0 收藏0
  • [Spring cloud 一步步實現(xiàn)廣告系統(tǒng)] 4. 通用代碼模塊設(shè)計

    摘要:對的配置和行為進行定制修改匹配路由請求規(guī)則注冊自定義的和添加靜態(tài)資源處理器添加自定義視圖控制器添加自定義方法參數(shù)處理器配置消息轉(zhuǎn)換器清空所有轉(zhuǎn)換器做一個好人。博客園掘金簡書頭條知乎 一個大的系統(tǒng),在代碼的復(fù)用肯定是必不可少的,它能解決: 統(tǒng)一的響應(yīng)處理(可以對外提供統(tǒng)一的響應(yīng)對象包裝) showImg(https://segmentfault.com/img/remote/146000...

    since1986 評論0 收藏0
  • Spring Boot 最流行的 16 條實踐解讀!

    摘要:來源是最流行的用于開發(fā)微服務(wù)的框架。以下依次列出了最佳實踐,排名不分先后。這非常有助于避免可怕的地獄。推薦使用構(gòu)造函數(shù)注入這一條實踐來自的項目負責人。保持業(yè)務(wù)邏輯免受代碼侵入的一種方法是使用構(gòu)造函數(shù)注入。 showImg(https://mmbiz.qpic.cn/mmbiz_jpg/R3InYSAIZkHQ40ly9Oztiart2lESCyjCH0JwFRp3oErlYobhibM...

    Ethan815 評論0 收藏0
  • SpringBoot統(tǒng)一響應(yīng)體解決方案

    摘要:前言最近在優(yōu)化自己之前基于的統(tǒng)一響應(yīng)體的實現(xiàn)方案。但是的狀態(tài)碼數(shù)量有限,而隨著業(yè)務(wù)的增長,狀態(tài)碼無法很好地表示業(yè)務(wù)中遇到的異常情況。 前言 最近在優(yōu)化自己之前基于Spring AOP的統(tǒng)一響應(yīng)體的實現(xiàn)方案。 什么是統(tǒng)一響應(yīng)體呢?在目前的前后端分離架構(gòu)下,后端主要是一個RESTful API的數(shù)據(jù)接口。 但是HTTP的狀態(tài)碼數(shù)量有限,而隨著業(yè)務(wù)的增長,HTTP狀態(tài)碼無法很好地表示業(yè)務(wù)中遇...

    figofuture 評論0 收藏0

發(fā)表評論

0條評論

call_me_R

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<