手把手教你如何玩轉統一異常處理

個人公衆號: MarkerHub,Java網站: https://markerhub.com

更多精選文章請點擊:Java筆記大全.md前端

小Hub領讀:

其實處理異常的方式其實都比較固定,java

  1. 自定義異常
  2. 全局異常處理,通常都是@ControllerAdvice+@ExceptionHandler的組合
  3. 返回的消息類能夠根據需求加上錯誤碼,錯誤消息等參數

學廢了嗎?哈哈git


情景引入

我:呼嚕呼嚕呼嚕呼嚕。。。。。。。。github

小白:起牀起牀起牀,,快點起牀。。。web

我:小白,又遇到什麼事了,這麼火急火燎的,年輕人,作事要穩重spring

小白:我遇到了一個很嚴重的問題,想讓你指導指導我!sql

我:哎喲,此次這麼虛心請教啦,那我不生氣了,,你說,怎麼了呢?數據庫

小白:就是,我在開發的過程當中,由於是團隊開發,因此,有時候邏輯就對不上,而後就會 莫名其妙的出現一些問題,而且顯示的效果很是難堪,並且也不容易發現問題,每次都要查看後臺才能知道問題,但是部署到服務器以後,都只能看 Log 日誌來定位問題。json

我:對呀,這項目開發原本就是一個團隊的事情,這是很正常的事,有什麼大驚小怪呢?windows

小白:因此,我想着,有沒有什麼辦法,能夠針對系統中的異常(未知和已知)可以友好的進行顯示呢?這樣,咱們在交流的時候也相對更加方便呀,不然,老是看着一堆亂七八糟的錯誤,挺心煩的。 我:我好像理解了你的意思。你就是想着,能對系統中的異常可以友好顯示或者說能方便大家團隊開發嘛。

小白:對的對的,就是這麼個意思。

我:這固然有了,並且你如今遇到的這個問題,其實在每一個系統中都應該有進行處理,雖然它比較簡單,可是不容小視,也有不少重要的東西的呢~ 那就好好聽課吧!

小白:真開心,,,,,火燒眉毛了

導入

問題

針對 Web 項目來講,咱們都知道,通常都是一個團隊進行開發,而不會是一我的單打獨鬥,而且開發團隊還有先後端的人員,那麼有必定的規範就是必不可少的。

咱們可能都遇到過一個問題,就是開發環境和正式上線的環境是有很大的差異的。開發環境是針對咱們開發人員,而正式環境是一種以用戶的角度來審視咱們的整個系統。想一想一個問題,若是遇到了咱們在開發中沒有碰到的異常,而用戶卻發現了,用戶體驗是否是會很是很差,並且這是咱們的一個大忌。

既然如此,咱們也知道,開發過程當中,有如此多的異常可能會出現,那麼裏面就包含着咱們已經考慮到了的,然而還有一些隱藏的異常倒是咱們可能忽視的,因此,爲了可以將那些潛在的異常不被用戶直接發現,而影響用戶體驗,這 --------- 異常統一處理,,,就必不可少!

異常統一處理

定義:簡單點說,就是針對咱們系統中的異常,給予必定規範的處理結果。(好比,默認的狀況,就是將異常堆棧信息直接打印到頁面,然而這種是極其醜陋的)

出現的情景

開發人員預測獲得的自定義異常

在開發中,開發人員對某些可能出現的情形是能夠預知的,這時候是一種主動處理的狀態。

開發人員沒法預測的系統異常

在開發中,存在着開發人員沒法全面思考到的異常,那麼這時候就是一種潛在性的可能異常狀態。

前端和後臺交互異常

因爲先後端的分離,並且先後端的開發方向也存在着差別,那麼就有可能致使異常的出現。

開發環境

  • windows 7 + 渣渣筆記本
  • IDEA + SpringBoot + Mybatis +Mysql

開發步驟

建立自定義異常

分析:在系統中,存在着系統異常和咱們人爲的自定義異常,因此,爲了可以有效的針對不一樣異常進行處理,那麼擁有咱們自定義的異常類是很是有必要的。

package com.hnu.csapp.exception;
/**
 * @ Author     :scw
 * @ Date       :Created in 下午 3:18 2018/11/29 0029
 * @ Description:自定義異常,爲了區分系統異常和更方便系統的特定一些處理
 * @ Modified By:
 * @Version: 1
 */
public class MyException extends RuntimeException{
    //錯誤碼
    private Integer code;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public MyException(String message) {
        super(message);
    }
    /**
     * 構造器重載,主要是本身考慮某些異常自定義一些返回碼
     * @param code
     * @param message
     */
    public MyException(Integer code,String message) {
        super(message);
        this.code = code;
    }
}

建立消息返回的包裝實體

分析:對於後臺返回給前端的數據來講,咱們不少狀況都是返回的 JSON 格式的數據(固然,並非侷限於這一種),那麼 JSON 是一種格式化的形式,因此,咱們應該有效的針對這樣的形式來給予必定的返回規範,這樣也方便前端對於咱們返回數據的解析。

好比:不少狀況通常是以下的格式:

{
  "retCode": 200,  //經過狀態碼能夠獲得消息是否返回正常,而後再決定是否去解析data域的內容
    "data": {        //返回的數據內容
    }
    "retMes": success  //返回的提示內容
}

因此,咱們能夠定義以下的類:

package com.hnu.csapp.exception;
/**
 * @ Author     :scw
 * @ Date       :Created in 下午 3:09 2018/11/29 0029
 * @ Description:異常處理實體包裝類,本身用泛型進行寫,擴展性強點
 * @ Modified By:
 * @Version: 1
 */
public class Result<T> {
    //返回碼
    private Integer code;
    //返回消息
    private String msg;
    //返回數據
    private T data;
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public T getData() {
        return data;
    }
    public void setData(T data) {
        this.data = data;
    }
}

定義一系列的枚舉返回信息

分析:在系統中,咱們應該有統一的某些編碼對應某些內容,這樣可以方便開發人員進行及時的處理。

package com.hnu.csapp.exception;
/**
 * @ Author     :scw
 * @ Date       :Created in 下午 3:23 2018/11/29 0029
 * @ Description:自定義一些返回狀態碼,便於本系統的使用,本身先定義以下的,有須要就後續補充
 * @ Modified By:
 * @Version: 1
 */
public enum ResultEnum {
    /**
     * 成功.: 200 (由於http中的狀態碼200通常都是表示成功)
     */
    SUCCESS(200,"成功"),
    /**
     * 系統異常. ErrorCode : -1
     */
    SystemException(-1,"系統異常"),
    /**
     * 未知異常. ErrorCode : 01
     */
    UnknownException(01,"未知異常"),
    /**
     * 服務異常. ErrorCode : 02
     */
    ServiceException(02, "服務異常"),
    /**
     * 業務錯誤. ErrorCode : 03
     */
    MyException(03,"業務錯誤"),
    /**
     * 提示級錯誤. ErrorCode : 04
     */
    InfoException(04, "提示級錯誤"),
    /**
     * 數據庫操做異常. ErrorCode : 05
     */
    DBException(05,"數據庫操做異常"),
    /**
     * 參數驗證錯誤. ErrorCode : 06
     */
    ParamException(06,"參數驗證錯誤");
    private Integer code;
    private String msg;
    ResultEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
    public Integer getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
}

定義消息返回工具類

分析:對於消息的返回,這是一個很是普通的工做,因此,咱們能夠將其封裝一個工具類,可以進行有效代碼的封裝,減小多餘的代碼。

package com.hnu.csapp.exception;
/**
 * @ Author     :scw
 * @ Date       :Created in 下午 3:12 2018/11/29 0029
 * @ Description:返回消息處理的工具類,主要是處理操做成功和失敗的一些內容
 * @ Modified By:
 * @Version: 1
 */
public class ResultUtil {
    /**
     * 操做成功的處理流程
     * @param object
     * @return
     */
    public static Result getSuccess(Object object){
        Result result = new Result();
        //設置操做成功的返回碼
        result.setCode(200);
        //設置操做成功的消息
        result.setMsg("成功");
        result.setData(object);
        return result;
    }
    /**
     * 重載返回成功的方法,由於有時候咱們不須要任何的消息數據被返回
     * @return
     */
    public static Result getSuccess(){
        return getSuccess(null);
    }
    /**
     * 操做失敗的處理流程
     * @param code 錯誤碼
     * @param msg 錯誤消息
     * @param o   錯誤數據(其實這個通常都不須要的,由於都已經返回失敗了,數據都不必返回)
     * @return
     */
    public static Result getError(Integer code, String msg, Object o){
        Result result = new Result();
        result.setCode(code);
        result.setMsg(msg);
        result.setData(o);
        return result;
    }
    /**
     * 重載,操做失敗的方法(由於操做失敗通常都不須要返回數據內容)
     * @param code
     * @param msg
     * @return
     */
    public static Result getError(Integer code, String msg){
        return getError(code, msg, null);
    }
}

定義異常統一處理類(重點)

分析:這是如何實現異常統一處理的關鍵地方,並且我也將不一樣的處理情形,進行了分開註釋,因此,你們必定能夠認真的看代碼,我相信你必定可以明白。

package com.hnu.csapp.exception;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
/**
 * @ Author     :scw
 * @ Date       :Created in 下午 3:20 2018/11/29 0029
 * @ Description:異常統一處理類,方便用戶能夠更加友好的看到錯誤信息
 * @ Modified By:
 * @Version: 1
 */
@ControllerAdvice
public class ExceptionHandle {
    //增長異常日誌打印
    private final static Logger logger = LoggerFactory.getLogger(ExceptionHandle.class);
    //設置異常錯誤的頁面
    public static final String DEFAULT_ERROR_VIEW = "error";
    /**
     * 以json的格式進行返回內容(開發環境通常我的是用這個比較好)
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public Object handle(HttpServletRequest req, Exception e){
        //若是是自定義的異常
        if(e instanceof MyException){
            MyException myException = (MyException)e;
            return ResultUtil.getError(myException.getCode(),myException.getMessage());
        }else{
            //若是是系統的異常,好比空指針這些異常
            logger.error("【系統異常】={}",e);
            return ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
        }
    }
    /**
     * 判斷是不是Ajax的請求
     * @param request
     * @return
     */
    public boolean isAjax(HttpServletRequest request){
        return (request.getHeader("X-Requested-With") != null
                &&
                "XMLHttpRequest".equals(request.getHeader("X-Requested-With").toString()));
    }
    /*
    //備註:
    //這個是正式項目完成以後的錯誤統一處理(開發狀況先用上面的的)
    //咱們在開發過程當中仍是用json格式的會好一些,要否則看錯誤麻煩
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, Exception e) throws Exception {
        e.printStackTrace();
        //判斷是不是Ajax的異常請求(若是是Ajax的那麼就是返回json格式)
        if(isAjax(req)){
            //若是是自定義的異常
            if(e instanceof MyException){
                MyException myException = (MyException)e;
                return ResultUtil.getError(myException.getCode(),myException.getMessage());
            }else{
                //若是是系統的異常,好比空指針這些異常
                logger.error("【系統異常】={}",e);
                return ResultUtil.getError(ResultEnum.SystemException.getCode(),ResultEnum.SystemException.getMsg());
            }
        }else{
            //若是是系統內部發生異常,那麼就返回到錯誤頁面進行友好的提示
            ModelAndView mav = new ModelAndView();
            //這些就是要返回到頁面的內容(其實不用都行,反正用戶也不懂,不必在頁面顯示均可以,先寫着吧)
            mav.addObject("exception", e);
            mav.addObject("url", req.getRequestURL());
            mav.setViewName(DEFAULT_ERROR_VIEW);
            return mav;
        }
    }
    */
}

定義異常處理頁面

分析:這個的話,其實主要是在正式環境纔有,由於咱們在測試環境的時候,通常都仍是會將錯誤以 JSON 或者堆棧的格式顯示在頁面,而當上線的時候,那麼就必定要有一個統一的錯誤頁面,這樣就可以讓用戶發現不了是系統出現了哪些問題。

這個的話,我將具體的 「error」 頁面放在了個人另一篇博文裏面,歡迎你們進行閱讀。

https://blog.csdn.net/Cshnusc...

效果

1: 開發環境

2: 正式環境

分析:當出現異常的時候,則顯示以下的頁面。(該頁面是參考一個博友的,感受挺有意思,,老司機~)

總結

異常統一處理,或許咱們看起來實現很是簡單,然而,其餘它包含的思想倒是一種大局思想,這是咱們開發人員在開發過程當中都應該關注的點,咱們並非只須要關注咱們每一個人開發的那點任務,而要以一種全局的角度去審視整個項目,這樣也可以提高咱們開問題的高度。

異常統一處理,是每一個項目都存在的,只是可能實現的方式不同而已,或者顯示的效果不同而已,這些都不是關鍵的地方。

異常統一處理這個問題,並非很難,可是這個能夠幫助咱們延伸到其餘的一些相關的開發層面的知識,好比:

  • 登陸攔截
  • 權限管理
  • 日誌管理
  • 事務處理
  • 數據控制和過濾
  • 。。。

因此,咱們應該學會從一個問題,發散的看到相關相似的問題,這樣,咱們的系統纔會更加健壯,高效和可擴展性強。

推薦閱讀

Java筆記大全.md

太讚了,這個Java網站,什麼項目都有!https://markerhub.com

這個B站的UP主,講的java真不錯!

相關文章
相關標籤/搜索