springboot優雅的做全局異常處理(完整代碼+已運行使用)

首先我們需要知道的是,java異常的基礎知識和springboot對異常處理是如何支持的。Java異常的基礎知識大家可以參考菜鳥教程的java教程。

  1. springboot對異常處理的支持

在springboot中,我們只需要定義一個全局異常處理類(用@ControllerAdvice),不需要在每個controller類重複定義。在全局異常處理類中,我們使用@ExceptionHandler指定每個方法接收並處理哪種異常。經常我們還會使用@Slf4j和@ResponseBody做日誌記錄和返回json格式的數據。我定義的全局異常處理類如下:

 

  1. 優雅的處理全局異常

在上面定義的全局異常類中,我們可以看到有許多奇奇怪怪的類和方法,如BizException、ResultData等。

這些類是我自己定義的,作用是讓全局異常更優雅,更易擴展,下面我將詳細介紹如何優雅的處理全局異常。

2.1 總概

我寫的全局異常處理包括了一個通用接口CommonException,一個自定義異常枚舉類ExceptionEnum、一個公共異常封裝類BizException、一個全局異常處理類GlobalExceptionHandler。

 

2.2 通用接口CommonException

package com.hy.exception;
/**
 * Description:
 * 作爲一個公共的異常
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/21 16:55
 * @since JDK 1.8
 */
public interface CommonException {

    public int getExceptionCode();

    public String getExceptionMsg();

}

只定義了兩個方法,目的是實現抽象爲擴展提供可能(好吧,我知道你沒聽懂,不要着急,隨着後面的閱讀,我相信你會慢慢明白的)

2.3 自定義異常枚舉類

package com.hy.exception;

/**
 * Description:
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/21 16:57
 * @since JDK 1.8
 */
public enum ExceptionEnum implements CommonException {

    /**
     * 所有的代碼內部錯誤都拋出該異常
     */
    INTERNAL_ERROR(500, "內部服務異常"),

    /**
     * 如參數校驗未通過,參數爲空等拋出該異常
     */
    PARAMS_ERROR(100, "參數異常"),

    /**
     * 對爬蟲等惡意訪問拋出該異常
     */
    REQUEST_RATE_LIMIT(300, "請求超過速率"),

    /**
     * token校驗未通過拋出該異常
     */
    TOKEN_ERROR(101, "token異常"),

    /**
     * token過期拋出該異常
     */
    TOKEN_EXPIRED(102, "token過期");

    private int exCode;
    private String exMsg;

    ExceptionEnum(int exCode, String exMsg) {
        this.exCode = exCode;
        this.exMsg = exMsg;
    }

    @Override
    public int getExceptionCode() {
        return exCode;
    }

    @Override
    public String getExceptionMsg() {
        return exMsg;
    }

}

可以看到,自定義的異常枚舉類包含了exCode和exMsg兩個屬性,同時實現了CommonException接口,將getExceptionCode和getExceptionMsg兩個方法分別實現,返回exCode和exMsg屬性。

其中exCode和exMsg是我們根據自己需要完成的業務和可能出現的情況自定義的(值得一提的是,在經過一段時間完整項目Demo的開發後,發現對出現的偶然性錯誤我們常手工拋出異常,代替以往在單獨代碼中的輸出錯誤或者提示重輸)

這裏我們開始了第一個優雅:讓枚舉類繼承CommonException接口。

在這裏繼承CommonException接口後,原有的功能不多不少,但是卻對ExceptionEnum做了抽象處理,在後面的BizException類中定義一個方法要傳入ExceptionEnum對象作爲參數時,我們對這個參數的類型定義不用ExceptionEnum而用CommonException,這樣我們以後如果需要添加行的枚舉類,就不用在ExceptionEnum中修改代碼,只需要再新建一個類implements CommonException然後定義要添加的異常類型就可以了。

2.4 公共異常封裝類BizException

package com.hy.exception;

/**
 * Description:
 * 公共異常封裝類
 * 用於在邏輯代碼中拋出異常時的封裝
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/22 15:44
 * @since JDK 1.8
 */
public class BizException extends Exception implements CommonException{

    private int exCode;

    private String exMsg;

    @Override
    public int getExceptionCode() {
        return exCode;
    }

    @Override
    public String getExceptionMsg() {
        return exMsg;
    }

    /**
     * 使用自定義的異常枚舉類構造異常
     * @param exceptionEnum
     */
    public BizException(CommonException exceptionEnum){

        this.exCode = exceptionEnum.getExceptionCode();
        this.exMsg = exceptionEnum.getExceptionMsg();
    }

    public BizException(int exCode, String exMsg){
        this.exCode = exCode;
        this.exMsg = exMsg;
    }

    public BizException(int exCode){
        this.exCode = exCode;
        this.exMsg = "未知錯誤";
    }

    public BizException(String exMsg){
        this.exCode = 10001;
        this.exMsg = exMsg;
    }
}

我們使用這個類來封裝自定義的異常信息再拋出,這樣我們在GlobalExceptionHandler類中就可以用一個單獨的被@ExceptionHandler(value = BizException.class)註解的方法來處理自定義的異常。

這個類中繼承Exception是爲了將這個類變爲異常類,實現CommonException只是爲了複用,少寫點代碼。

同時,我們應該注意這個類的構造方法:

有一個接收CommonException類型的構造方法可以直接接收一個枚舉類型,其他的則是另外零散的自定義異常信息的構造方法。

2.5 全局異常捕獲處理類GlobalExceptionHandler

package com.hy.exception;

import com.hy.vo.ResultData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * Description:
 * 全局異常捕獲類
 * 所有的異常都會在這個類中處理
 *
 * @author houyi
 * @version 1.0
 * @date 2020/1/22 20:50
 * @since JDK 1.8
 */
@ControllerAdvice
@Slf4j
@ResponseBody
public class GlobalExceptionHandler {

    /**
     * 捕獲自定義的幾種異常
     *
     * @param ex
     * @return 異常碼和異常信息封裝的ResultData對象
     */
    @ExceptionHandler(value = BizException.class)
    public ResultData handlerBizException(Exception ex){

        // 向下轉型使BizException的自定義的不同於父類Exception的方法可以使用
        BizException bizException = (BizException)ex;

        // 獲得異常碼和異常信息
        int exCode = bizException.getExceptionCode();
        String exMsg = bizException.getExceptionMsg();

        // 將異常碼和異常信息封裝成通用數據返回類型
        ResultData resultData = ResultData.fail(exCode, exMsg);

        // 對異常做日誌記錄,方便項目正式運行時發生異常後尋找異常發生點
        log.error(exCode + ":" +exMsg,bizException);

        // 向前端返回數據
        return resultData;
    }

    /**
     * 系統拋出的異常
     * @param ex
     * @return 固定異常碼10002 和 異常信息封裝的ResultData對象
     */
    @ExceptionHandler(value = Exception.class)
    public ResultData handlerException(Exception ex){

        // 對所以其他異常,統一異常碼爲10002,並封裝
        ResultData resultData = ResultData.fail(10002,ex.getMessage());

        // 日誌記錄
        log.error(10002 + ":" +ex.getMessage(),ex);

        // 返回數據
        return resultData;
    }

}

一路走來,我們的主角終於要上場了。在全局異常捕獲處理類中,我只定義了兩個類,一個BizException的捕獲處理和除BizException的其他所有異常類的捕獲處理。可以看到,不管加多少自定義異常枚舉類,我們都只要加自定義異常枚舉類。

2.6 測試

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章