首先我們需要知道的是,java異常的基礎知識和springboot對異常處理是如何支持的。Java異常的基礎知識大家可以參考菜鳥教程的java教程。
- springboot對異常處理的支持
在springboot中,我們只需要定義一個全局異常處理類(用@ControllerAdvice),不需要在每個controller類重複定義。在全局異常處理類中,我們使用@ExceptionHandler指定每個方法接收並處理哪種異常。經常我們還會使用@Slf4j和@ResponseBody做日誌記錄和返回json格式的數據。我定義的全局異常處理類如下:
- 優雅的處理全局異常
在上面定義的全局異常類中,我們可以看到有許多奇奇怪怪的類和方法,如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 測試