1. 程式人生 > >SpringBoot系列: Spring支援的異常處理方式

SpringBoot系列: Spring支援的異常處理方式

===================================
檢視函式返回 status code 的方式
===================================
Spring 有一個專門的列舉型別 HttpStatus, 比如 HttpStatus.NOT_FOUND
1. 檢視函式返回 ResponseEntity 型別的物件.
2. 在 exception 類加註解 @ResponseStatus, 一旦檢視函式丟擲這個異常, Spring 就會自動返回設定的 status code.
3. 在檢視函式上加 @ResponseStatus, 只要該函式沒有報異常, Spring 就會自動返回設定的 status code.

 

===================================

方式一: 在業務 Controller 內部增加異常檢視函式
===================================
在我們的 Controller 類中專門加一個或幾個處理異常的檢視函式, 這些異常處理檢視需要加 @ExceptionHandler 註解.

將 @ExceptionHandler 註解的加到異常檢視函式上, 也可以將 @ResponseStatus 註解一起加上. 異常檢視函式的引數和普通檢視函式的引數不一樣, 能傳入 Exception/HttpServletRequest/HttpServletResponse/HttpSession/Principle 等引數, 但是不能傳入 Model 之類的引數, 檢視方法的返回型別可以是: void/String 或 ModelAndView.

該方式缺點是: 這種異常處理僅僅在本 controller 有效, 不太好做到通用的異常處理. 當然也可設計一個通用的 Controller base 類, 在 base 類中加上異常處理檢視函式, 所有業務 controller 都繼承這個基類.


===================================
方式二: 實現一個基於 HandlerExceptionResolver 介面的 bean
===================================
在 Spring Web 專案中, 如果我們的 bean 實現了 HandlerExceptionResolver 介面, 一旦某個 controller 丟擲異常, 這個 bean 會截獲並處理異常.
該方法的缺點是: 不太好返回具體的報錯內容.

一般情況下, 我們的 bean 並不直接實現 HandlerExceptionResolver 介面, 而是繼承 SimpleMappingExceptionResolver 類.

通常做法是, 我們先自定義一個 SimpleMappingExceptionResolver 子類, 實現 doResolveException() 方法, 然後在 Mvc Configruation 配置類中, 宣告一個名為 simpleMappingExceptionResolver 的 bean. 之所以繼承 SimpleMappingExceptionResolver 類, 是因為它已經實現了很多有用的功能, 我們直接使用即可, 比如:
1. 設定 exception 和 view 檢視的 mapping 關係等
2. 設定預設的異常處理檢視名
3. 異常 log 功能 (需啟用)

除了 SimpleMappingExceptionResolver 類之外, Spring 還有其他幾個 HandlerExceptionResolver 實現類, 作用分別是:
1. ExceptionHandlerExceptionResolver, 用來截獲標註 @ExceptionHandler 註解的檢視函式異常, @ExceptionHandler 加在專門的異常處理檢視函式上.
2. ResponseStatusExceptionResolver, 用來截獲和 @ResponseStatus 註解相關的異常, @ResponseStatus 可以加在 Exception 類或檢視函式上.
3. DefaultHandlerExceptionResolver, 用來截獲標準的 Spring exception, 並將異常轉成相應 Http status code, 比如 404 等.


===================================
方式三: 使用 @ControllerAdvice 註解宣告一個異常處理類
===================================
@ControllerAdvice 是 Spring 3.2 之後引入的. 在一個業務 controller 丟擲異常後, @ControllerAdvice 標註的類會截獲該異常. @ControllerAdvice 還有一個兄弟 @RestControllerAdvice 註解.
@ControllerAdvice 標註在類上, 具體異常檢視函式仍然通過加 @ExceptionHandler 註解宣告的, 該檢視函式能傳入 Exception/HttpServletRequest/HttpServletResponse/HttpSession/Principle 等引數, 但是不能傳入 Model 之類的引數, 檢視方法的返回型別可以是: void/String 或 ModelAndView.

@ControllerAdvice 優點有:
1. @ControllerAdvice 處理機制能接管所有 controller 類的異常, 更容易實現全域性統一的異常處理.
2. 可以自由組合不同的 Exception 的異常處理檢視.
3. 異常檢視函式可以用 @ResponseStatus 註解指定返回碼. 檢視函式返回型別可以是 void/String 或 ModelAndView, 所以很容易返回報錯內容.
4. 我們 @ControllerAdvice 註解類最好繼承至 ResponseEntityExceptionHandler, 該 ResponseEntityExceptionHandler 類已經內建了很多標準異常的異常處理檢視, 所以我們不需要再關心標準異常的處理, 只需要關心和業務相關的異常即可.

 

===================================
Spring Boot 預設的出錯處理
===================================
Spring MVC 沒有提供預設的 fall-back 出錯頁, 而 Spring Boot 預設情況下就有很完善的報錯處理機制, 一旦檢視函式報錯, Spring boot 先看有沒有對映 /error 路徑的檢視, 有的話渲染該檢視, 沒有的話, 會展現 "Whitelabel Error Page", 該頁面能顯示出 Status code 和詳細的報錯資訊. 如果是 Restful 請求的話, Whitelabel 報錯也是 json 格式的反饋.

比如: 

$> curl -H "Accept: application/json" http://localhost:8080/no-such-page

返回是: 

{"timestamp":"2018-04-11T05:56:03.845+0000","status":404,"error":"Not Found","message":"No message available","path":"/no-such-page"}

預設預設的異常處理路徑是 /error,  也可以通過 server.error.path 屬性修改. 

===================================
推薦的做法:
===================================
1. 在一個專案中, 僅使用一種異常處理方法, 不要組合使用, 因為有可能和我們的預期不一致.
2. 推薦使用全域性性的異常處理, 而不是 Controller 級別的異常處理; 推薦 @ControllerAdvice, 而不是基於 HandlerExceptionResolver 介面的 bean.
3. @ControllerAdvice 註解類最好繼承至 ResponseEntityExceptionHandler, 該 ResponseEntityExceptionHandler 類已經內建了很多標準異常的異常處理檢視, 所以我們不需要再關心標準異常的處理, 只需要關心和業務相關的異常即可.

需要說明的是, 只有進入 Controller 的異常, @ControllerAdvice 才能捕獲, 對於攔截器的異常和 url寫錯這類404 這樣的異常, 是捕獲不到的, 當然一般情況下我們也沒有必要處理這些異常.

下面是一個簡單的示意程式碼: 

class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // 針對不同的異常, 定義其處理檢視函式
    // some view functions
        
        
    // 對應 uncaughted 異常, 使用下面這個檢視函式來兜底
    public static final String DEFAULT_ERROR_VIEW = "error";
    @ExceptionHandler(value = Exception.class)
    public ModelAndView defaultErrorHandler(HttpServletRequest req, HttpServletResponse rep, Exception e) throws Exception {
       //針對 500 錯誤, 需要記錄 error 日誌. 
       logger.error(e.getMessage(),e);
       if (req.getHeader("Accept").contains("application/json"))
       { 
           //write json to HttpServletResponse
           return null;
       }
       else{
            ModelAndView mav = new ModelAndView();
            mav.addObject("exception", e);
            mav.addObject("url", req.getRequestURL());
            mav.setViewName(DEFAULT_ERROR_VIEW);
            return mav;
        }
    }    
}


===================================
參考:
===================================
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
https://blog.csdn.net/aiyaya_/article/details/78725755
http://blog.didispace.com/springbootexception/
https://blog.csdn.net/chinrui/article/details/71036544
http://tengj.top/2018/05/16/springboot13/