異常、異常處理和統一異常處理

異常、異常處理和統一異常處理

日常求贊,感謝老闆。

歡迎關注公衆號:其實是白羊。乾貨持續更新中…

廢話不多說,先來幾個基礎墊吧墊吧。

一、異常

顧名思義,異常就是不正常唄,這是一種現象,也是Java爲我們提供的程序安全退出的通道。一旦出現異常,異常處理機制會將代碼執行交給異常處理器,而不再執行原有方法。

爲了描述不同的這種不正常現象,我們定義了各種各樣的異常類型。

首先來張圖:

轉自https://blog.csdn.net/qq_29229567

1)Throwable

沒錯它屬所有異常和錯誤的“祖宗”,下面介紹幾個常用的方法:

  1. getMessage():獲取detail message即相關的錯誤描述信息。
  2. toString():返回包含異常類名+getMessage()。
  3. printStackTrace():打印詳細異常信息和異常拋出路徑(不推薦使用,可使用log代替)。

2)Error

Error是Throwable的子類之一,包含的方法主要來自於繼承自Throwable的那些。Error我們開發者接觸到的不多,一般都是和虛擬機相關的問題,如:系統崩潰、虛擬機錯誤、系統資源如內存不足等,如:OutOfMemoryError等。Error靠程序自身是無法解決的,一般JVM會選擇終止程序。

3)Exception

Exception也是Throwable的子類之一。相較於Error而言,但是它可是和我們開發者有很大的關係,也經常會遇到他或他的子類,Exception的子類是用來抽象主要都是程序自身出現類問題,我們開發者需要關注和處理這些問題,保證程序業務正常。

Exception擁有大量的子類,但是大致分爲兩種:

  1. RuntimeException以及他的子類:這類異常都屬於不可檢查異常(unchecked exceptions)
  2. 除去1中的非運行時異常:這類異常屬於可檢查異常(checked exceptions)

不可檢查異常:程序經過編譯時,編譯器不會要求對此類異常進行處理(throws/try-catch)。RuntimeException以及他的子類和Error都屬於不可檢查異常,對於RuntimeException的子類我們開發者要特別注意,因爲編譯器不會提示我們,我們要給與合適的處理(但像空指針、數組下標越界等異常都可以通過代碼規避掉,還有些如NumberFormatException等在不確定的情況下要使用異常處理機制哦,舉例:在解析JSON字符串形式的數字時,可能存在數字格式錯誤)。

可檢查異常:程序經過編譯時,編譯器會要求對此類異常進行處理(throws/try-catch)。Exception子類中除去RuntimeException以及他的子類的異常都是可檢查異常。處理方式也就是:throws/try-catch,下面會細講。

二、異常處理

在出現可檢查異常和可能出現運行時異常時,需要採用異常處理機制。常見的異常處理機制:

  1. 使用throws向方法棧上層拋出:

    public void test() thros NumberFormatException{
        //TODO
        //拋出一個可檢查異常或可能拋出運行時異常
    }
    
  2. 使用try-catch處理相應的異常:執行記錄日誌、返回給前端數據等操作(後面也會介紹使用統一異常處理來解決對異異常的處理)。

    之前寫的關於異常處理的運行流程(https://blog.csdn.net/zll_zll_zll_zll/article/details/89332827)在這裏既不贅述了。

三、統一異常處理

上面的內容都是偏向於理論,這裏我們討論下,實際項目中對於異常的處理方式。

而且在JavaWeb應用中,一旦發生異常,正常的代碼邏輯就不能執行了,而去執行異常處理:

我們想要的:

  1. 前後端分離時:我們要返回給用戶固定格式的包含錯誤信息的數據,有好的提示用戶。
  2. 前後端未分離時:我們要跳轉到錯誤頁面(對用戶友好),而不是默認情況下的直接把錯誤信息打印在頁面上。

爲了實現上面的:

  1. 發生可檢查異常時:如果發生在非Controller層,我們一般會使用throws來向上拋出異常,知道Controller層,如果前後端分離那就返回固定數據,未分離則跳轉到錯誤頁面。但是會寫很多的try-catch代碼,不僅不美觀不易讀、而且不規範難維護(很難保證每個開發人員在catch裏的處理都很規範)。
  2. 對於不可檢查異常(運行時異常)來說:要麼只能靠祈禱每個開發人員都能寫出完美的代碼邏輯,保證不發生運行時異常(顯然不可能),要麼就在每個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)


歡迎關注公衆號:「其實是白羊」乾貨持續更新中…


在這裏插入圖片描述

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