Spring Boot2 系列教程(十三)Spring Boot 中的全局異常處理

在 Spring Boot 項目中 ,異常統一處理,可使用 Spring 中 @ControllerAdvice 來統一處理,也能夠本身來定義異常處理方案。Spring Boot 中,對異常的處理有一些默認的策略,咱們分別來看。html

默認狀況下,Spring Boot 中的異常頁面 是這樣的:java

咱們從這個異常提示中,也能看出來,之因此用戶看到這個頁面,是由於開發者沒有明確提供一個 /error 路徑,若是開發者提供了 /error 路徑 ,這個頁面就不會展現出來,不過在 Spring Boot 中,提供 /error 路徑其實是下下策,Spring Boot 自己在處理異常時,也是當全部條件都不知足時,纔會去找 /error 路徑。那麼咱們就先來看看,在 Spring Boot 中,如何自定義 error 頁面,總體上來講,能夠分爲兩種,一種是靜態頁面,另外一種是動態頁面。react

靜態異常頁面

自定義靜態異常頁面,又分爲兩種,第一種 是使用 HTTP 響應碼來命名頁面,例如 404.html、405.html、500.html ....,另外一種就是直接定義一個 4xx.html,表示400-499 的狀態都顯示這個異常頁面,5xx.html 表示 500-599 的狀態顯示這個異常頁面。web

默認是在 classpath:/static/error/ 路徑下定義相關頁面:ajax

此時,啓動項目,若是項目拋出 500 請求錯誤,就會自動展現 500.html 這個頁面,發生 404 就會展現 404.html 頁面。若是異常展現頁面既存在 5xx.html,也存在 500.html ,此時,發生500異常時,優先展現 500.html 頁面。spring

動態異常頁面

動態的異常頁面定義方式和靜態的基本 一致,能夠採用的頁面模板有 jsp、freemarker、thymeleaf。動態異常頁面,也支持 404.html 或者 4xx.html ,可是通常來講,因爲動態異常頁面能夠直接展現異常詳細信息,因此就沒有必要挨個枚舉錯誤了 ,直接定義 4xx.html(這裏使用thymeleaf模板)或者 5xx.html 便可。後端

注意,動態頁面模板,不須要開發者本身去定義控制器,直接定義異常頁面便可 ,Spring Boot 中自帶的異常處理器會自動查找到異常頁面。服務器

頁面定義以下:架構

頁面內容以下:app

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>5xx</h1>
<table border="1">
    <tr>
        <td>path</td>
        <td th:text="${path}"></td>
    </tr>
    <tr>
        <td>error</td>
        <td th:text="${error}"></td>
    </tr>
    <tr>
        <td>message</td>
        <td th:text="${message}"></td>
    </tr>
    <tr>
        <td>timestamp</td>
        <td th:text="${timestamp}"></td>
    </tr>
    <tr>
        <td>status</td>
        <td th:text="${status}"></td>
    </tr>
</table>
</body>
</html>
複製代碼

默認狀況下,完整的異常信息就是這5條,展現 效果以下 :

若是動態頁面和靜態頁面同時定義了異常處理頁面,例如 classpath:/static/error/404.htmlclasspath:/templates/error/404.html 同時存在時,默認使用動態頁面。即完整的錯誤頁面查找方式應該是這樣:

發生了 500 錯誤-->查找動態 500.html 頁面-->查找靜態 500.html --> 查找動態 5xx.html-->查找靜態 5xx.html。

自定義異常數據

默認狀況下,在 Spring Boot 中,全部的異常數據其實就是上文所展現出來的 5 條數據,這 5 條數據定義在 org.springframework.boot.web.reactive.error.DefaultErrorAttributes 類中,具體定義在 getErrorAttributes 方法中 :

@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = getError(request);
        HttpStatus errorStatus = determineHttpStatus(error);
        errorAttributes.put("status", errorStatus.value());
        errorAttributes.put("error", errorStatus.getReasonPhrase());
        errorAttributes.put("message", determineMessage(error));
        handleException(errorAttributes, determineException(error), includeStackTrace);
        return errorAttributes;
}
複製代碼

DefaultErrorAttributes 類自己則是在 org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration 異常自動配置類中定義的,若是開發者沒有本身提供一個 ErrorAttributes 的實例的話,那麼 Spring Boot 將自動提供一個 ErrorAttributes 的實例,也就是 DefaultErrorAttributes 。

基於此 ,開發者自定義 ErrorAttributes 有兩種方式 :

  1. 直接實現 ErrorAttributes 接口
  2. 繼承 DefaultErrorAttributes(推薦),由於 DefaultErrorAttributes 中對異常數據的處理已經完成,開發者能夠直接使用。

具體定義以下:

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        if ((Integer)map.get("status") == 500) {
            map.put("message", "服務器內部錯誤!");
        }
        return map;
    }
}
複製代碼

定義好的 ErrorAttributes 必定要註冊成一個 Bean ,這樣,Spring Boot 就不會使用默認的 DefaultErrorAttributes 了,運行效果以下圖:

自定義異常視圖

異常視圖默認就是前面所說的靜態或者動態頁面,這個也是能夠自定義的,首先 ,默認的異常視圖加載邏輯在 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController 類的 errorHtml 方法中,這個方法用來返回異常頁面+數據,還有另一個 error 方法,這個方法用來返回異常數據(若是是 ajax 請求,則該方法會被觸發)。

@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
        HttpStatus status = getStatus(request);
        Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(
                        request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));
        response.setStatus(status.value());
        ModelAndView modelAndView = resolveErrorView(request, response, status, model);
        return (modelAndView != null) ? modelAndView : new ModelAndView("error", model);
}
複製代碼

在該方法中 ,首先會經過 getErrorAttributes 方法去獲取異常數據(實際上會調用到 ErrorAttributes 的實例 的 getErrorAttributes 方法),而後調用 resolveErrorView 去建立一個 ModelAndView ,若是這裏建立失敗,那麼用戶將會看到默認的錯誤提示頁面。

正常狀況下, resolveErrorView 方法會來到 DefaultErrorViewResolver 類的 resolveErrorView 方法中:

@Override
public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        ModelAndView modelAndView = resolve(String.valueOf(status.value()), model);
        if (modelAndView == null && SERIES_VIEWS.containsKey(status.series())) {
                modelAndView = resolve(SERIES_VIEWS.get(status.series()), model);
        }
        return modelAndView;
}
複製代碼

在這裏,首先以異常響應碼做爲視圖名分別去查找動態頁面和靜態頁面,若是沒有查找到,則再以 4xx 或者 5xx 做爲視圖名再去分別查找動態或者靜態頁面。

要自定義異常視圖解析,也很容易 ,因爲 DefaultErrorViewResolver 是在 ErrorMvcAutoConfiguration 類中提供的實例,即開發者沒有提供相關實例時,會使用默認的 DefaultErrorViewResolver ,開發者提供了本身的 ErrorViewResolver 實例後,默認的配置就會失效,所以,自定義異常視圖,只須要提供 一個 ErrorViewResolver 的實例便可:

@Component
public class MyErrorViewResolver extends DefaultErrorViewResolver {
    public MyErrorViewResolver(ApplicationContext applicationContext, ResourceProperties resourceProperties) {
        super(applicationContext, resourceProperties);
    }
    @Override
    public ModelAndView resolveErrorView(HttpServletRequest request, HttpStatus status, Map<String, Object> model) {
        return new ModelAndView("/aaa/123", model);
    }
}
複製代碼

實際上,開發者也能夠在這裏定義異常數據(直接在 resolveErrorView 方法從新定義一個 model ,將參數中的model 數據拷貝過去並修改,注意參數中的 model 類型爲 UnmodifiableMap,即不能夠直接修改),而不須要自定義 MyErrorAttributes。定義完成後,提供一個名爲 123 的視圖,以下圖:

如此以後,錯誤試圖就算定義成功了。

總結

實際上也能夠自定義異常控制器 BasicErrorController ,不過鬆哥以爲這樣太大動干戈了,不必,前面幾種方式已經能夠知足咱們的大部分開發需求了。若是是先後端分離架構,異常處理還有其餘一些處理方案,這個鬆哥之後和你們聊。

關注公衆號【江南一點雨】,專一於 Spring Boot+微服務以及先後端分離等全棧技術,按期視頻教程分享,關注後回覆 Java ,領取鬆哥爲你精心準備的 Java 乾貨!

相關文章
相關標籤/搜索