異常、異常處理和統一異常處理
日常求贊,感謝老闆。
歡迎關注公衆號:其實是白羊。乾貨持續更新中…
廢話不多說,先來幾個基礎墊吧墊吧。
一、異常
顧名思義,異常就是不正常唄,這是一種現象,也是Java爲我們提供的程序安全退出的通道。一旦出現異常,異常處理機制會將代碼執行交給異常處理器,而不再執行原有方法。
爲了描述不同的這種不正常現象,我們定義了各種各樣的異常類型。
首先來張圖:
1)Throwable
沒錯它屬所有異常和錯誤的“祖宗”,下面介紹幾個常用的方法:
- getMessage():獲取detail message即相關的錯誤描述信息。
- toString():返回包含異常類名+getMessage()。
- printStackTrace():打印詳細異常信息和異常拋出路徑(不推薦使用,可使用log代替)。
2)Error
Error是Throwable的子類之一,包含的方法主要來自於繼承自Throwable的那些。Error我們開發者接觸到的不多,一般都是和虛擬機相關的問題,如:系統崩潰、虛擬機錯誤、系統資源如內存不足等,如:OutOfMemoryError等。Error靠程序自身是無法解決的,一般JVM會選擇終止程序。
3)Exception
Exception也是Throwable的子類之一。相較於Error而言,但是它可是和我們開發者有很大的關係,也經常會遇到他或他的子類,Exception的子類是用來抽象主要都是程序自身出現類問題,我們開發者需要關注和處理這些問題,保證程序業務正常。
Exception擁有大量的子類,但是大致分爲兩種:
- RuntimeException以及他的子類:這類異常都屬於不可檢查異常(unchecked exceptions)
- 除去1中的非運行時異常:這類異常屬於可檢查異常(checked exceptions)
不可檢查異常:程序經過編譯時,編譯器不會要求對此類異常進行處理(throws/try-catch)。RuntimeException以及他的子類和Error都屬於不可檢查異常,對於RuntimeException的子類我們開發者要特別注意,因爲編譯器不會提示我們,我們要給與合適的處理(但像空指針、數組下標越界等異常都可以通過代碼規避掉,還有些如NumberFormatException等在不確定的情況下要使用異常處理機制哦,舉例:在解析JSON字符串形式的數字時,可能存在數字格式錯誤)。
可檢查異常:程序經過編譯時,編譯器會要求對此類異常進行處理(throws/try-catch)。Exception子類中除去RuntimeException以及他的子類的異常都是可檢查異常。處理方式也就是:throws/try-catch,下面會細講。
二、異常處理
在出現可檢查異常和可能出現運行時異常時,需要採用異常處理機制。常見的異常處理機制:
-
使用throws向方法棧上層拋出:
public void test() thros NumberFormatException{ //TODO //拋出一個可檢查異常或可能拋出運行時異常 }
-
使用try-catch處理相應的異常:執行記錄日誌、返回給前端數據等操作(後面也會介紹使用統一異常處理來解決對異異常的處理)。
之前寫的關於異常處理的運行流程(https://blog.csdn.net/zll_zll_zll_zll/article/details/89332827)在這裏既不贅述了。
三、統一異常處理
上面的內容都是偏向於理論,這裏我們討論下,實際項目中對於異常的處理方式。
而且在JavaWeb應用中,一旦發生異常,正常的代碼邏輯就不能執行了,而去執行異常處理:
我們想要的:
- 前後端分離時:我們要返回給用戶固定格式的包含錯誤信息的數據,有好的提示用戶。
- 前後端未分離時:我們要跳轉到錯誤頁面(對用戶友好),而不是默認情況下的直接把錯誤信息打印在頁面上。
爲了實現上面的:
- 發生可檢查異常時:如果發生在非Controller層,我們一般會使用throws來向上拋出異常,知道Controller層,如果前後端分離那就返回固定數據,未分離則跳轉到錯誤頁面。但是會寫很多的try-catch代碼,不僅不美觀不易讀、而且不規範難維護(很難保證每個開發人員在catch裏的處理都很規範)。
- 對於不可檢查異常(運行時異常)來說:要麼只能靠祈禱每個開發人員都能寫出完美的代碼邏輯,保證不發生運行時異常(顯然不可能),要麼就在每個Controller層方法裏都加入try-catch保證萬一發生異常能有完成上面的操作(和1中面臨的困難一樣了)。
那麼怎麼才能,既能保證實現我們的預期,又能規避上面操作的缺點呢?
那就是做統一異常處理。
實現的方式/思路:
1)AOP方式:
使用Around Advice(環繞通知),在調用原方法的語句上加上try-catch,完成異常的捕獲、日誌記錄等其他操作。
@Around(value = "execution(public * *Controller.*(..))")
public BaseResult catchException(ProceedingJoinPoint joinPoint) {
try {
//統一返回數據類型(前後端爲分離時直接跳轉頁面即可)
BaseResult baseResult = new BaseResult(SUCCESS);
baseResult.setData(joinPoint.proceed());
return baseResult;
} catch (Throwable e) {
//捕獲異常
String className = joinPoint.getTarget().getClass().getName();
String methodName = joinPoint.getSignature().getName();
//打上LOG
logger.error(className + ":" + methodName + ":" + e);
return new BaseResult(FAIL);
}
}
怎麼樣?夠不夠驚豔?什麼,還嫌太麻煩?
2)@ControllerAdvice/@RestControllerAdvice+@ExceptionHandler
@Slf4j
@ControllerAdvice
@ResponseBody
//或者@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* @valid參數校驗異常處理
* @param e
* @return
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResultData validationException(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
if (bindingResult.hasErrors()) {
List<ObjectError> allErrors = bindingResult.getAllErrors();
List<String> messages = new ArrayList<>();
allErrors.forEach(p->{
FieldError fieldError = (FieldError) p;
log.error("參數格式錯誤:參數對象:{};字段:{};錯誤原因:{}", fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
messages.add(fieldError.getDefaultMessage());
});
return ResultData.error(StringUtils.join(messages,","));
}
return ResultData.error("參數格式錯誤");
}
/**
* 全局異常處理
* @param e
* @return
*/
@ExceptionHandler(Exception.class)
public ResultData globalExceptionHandler(Exception e) {
//MyBaseException自定義異常
if (e instanceof MyBaseException) {
//自定義異常
log.error("自定義異常",e);
return ResultData.error(e.getMessage());
} else {
//其他異常
log.error("未知異常", e);
//給用戶有好的提示(不能直接把異常信息傳回前端)
return ResultData.error("系統開小差了");
}
}
}
注意 @ExceptionHandler標識的方法的順序和攔截的順序一致,如果異常背前面的捕獲了,那麼後面的就能捕獲了,所以具體的異常要放在前面。
上面的代碼來自我自己的開源項目(地址:https://gitee.com/zhanglinlu/dms.git)中的com.zll.dms.aop.GlobalExceptionHandler,需要的可以參考下。
四、最後
點個贊啊親
如果你認爲本文對你有幫助,可以「在看/轉發/贊/star」,多謝
如果你還發現了更好或不同的想法,還可以在留言區一起探討下
更多項目:gitee地址(https://gitee.com/zhanglinlu)
歡迎關注公衆號:「其實是白羊」乾貨持續更新中…