springboot(11)——異常處理和自定義錯誤頁面

1.異常來源

要處理程序發生的異常,首先須要知道異常來自哪裏?html

1.前端錯誤的的請求路徑,會使得程序發生4xx錯誤,最多見的就是404,Springboot默認當發生這種錯誤的請求路徑,pc端響應的頁面以下前端

若是是移動端(手機端)將會響應json格式的數據,以下java

2.Springboot異常處理原理

爲何咱們請求錯誤的路徑,boot會給咱們返回一個上面錯誤頁面或者json格式數據呢?原理是怎樣的呢?
react

Springboot項目啓動以後,執行有@SpringBootApplication註解的啓動類的main方法,經過@EnableAutoConfiguration加載web

springbootAutoConfiguration.jar包下的META-INF/spring.factories中的全部配置類(這些配置類加載以後,會將每一個配置類裏面的組件注入容器而後使用)其中一個自動配置spring

ErrorMvcAutoConfiguration,經過代碼能夠看到用到了如下四個組件json

DefaultErrorAttributes、BasicErrorController、errorPageCustomizer、DefaultErrorViewResolver後端

其餘三個基本相似,當出現4xx或者5xx等錯誤時,errorPageCustomizer就會生效,this.properties.getError().getPath())並來到/error請求,核心代碼springboot

//errorPageCustomizer
@Value("${error.path:/error}")
    private String path = "/error";

而這個/error請求再由BasicErrorController處理,BasicErrorController是一個Controller,其中裏面有兩種處理方法,一種是HTML形式,服務器

一種是JSON格式。其中訪問者的信息能夠從getErrorAttributes從獲取。DefaultErrorAttributes是ErrorAttributes的實現類。

關鍵代碼

@RequestMapping(produces = "text/html") //HTML
    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 ? new ModelAndView("error", model) : modelAndView);
    }

    @RequestMapping
    @ResponseBody //JSON
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request,
                isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        return new ResponseEntity<>(body, status);
    }

當爲HTML模式時,就會構建一個resolveErrorView類,而resolverErrorView繼續調用ErrorViewResolver。關鍵代碼

protected ModelAndView resolveErrorView(HttpServletRequest request,
            HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
        for (ErrorViewResolver resolver : this.errorViewResolvers) {
            ModelAndView modelAndView = resolver.resolveErrorView(request, status, model);
            if (modelAndView != null) {
                return modelAndView;
            }
        }
        return null;
    }

在咱們沒有作自定義配置時,ErrorViewResolver就會指向DefaultErrorViewResolver。

static {
        //能夠用4xx,5xx文件名來統一匹配錯誤,可是會以精確優先的原則
        Map<Series, String> views = new EnumMap<>(Series.class);
        views.put(Series.CLIENT_ERROR, "4xx");
        views.put(Series.SERVER_ERROR, "5xx");
        SERIES_VIEWS = Collections.unmodifiableMap(views);
    }

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

    private ModelAndView resolve(String viewName, Map<String, Object> model) {
        //將錯誤代碼拼接到error後
        String errorViewName = "error/" + viewName;
        TemplateAvailabilityProvider provider = this.templateAvailabilityProviders
                .getProvider(errorViewName, this.applicationContext);
        //若是模版引擎可用就讓模版引擎進行解析如:Template/error/404
        if (provider != null) {
            return new ModelAndView(errorViewName, model);
        }
        //若是模版引擎不可用,就在靜態資源文件夾下找資源文件,error/404
        return resolveResource(errorViewName, model);
    }

3.如何定製錯誤、異常響應

明白了boot處理異常機制,咱們如何自定義異常響應規則呢?

第一種:pc端返回靜態錯誤頁面,手機端返回boot默認的json數據

若是項目中有模板引擎(jsp,thmeleaf,freemarker)的狀況下,能夠將錯誤頁面命名爲狀態碼.html放在模板引擎文件夾下的error文件夾下,

發生異常,無論是前端請求仍是後端程序錯誤會來到對應的錯誤頁面。能夠將錯誤頁面命名爲4xx和5xx匹配全部的錯誤,

可是優先返回精確狀態碼.html頁面;而且在模板引擎頁面能夠獲取以下相關信息

這裏模版引擎使用thmeleaf

4xx代碼

<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="'狀態碼:'+${status}"></title>
</head>
<body>
< img src="../images/404.jpg" style="width: 40%;">
<h1 th:text="'時間:'+${timestamp}"></h1>
<h1 th:text="'錯誤提示:'+${error}"></h1>
<h1 th:text="'異常對象:'+${exception}"></h1>
<h1 th:text="'異常信息:'+${message}"></h1>
</body>
</html>
5xx代碼
<!DOCTYPE html>
<html lang="en">
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title th:text="'狀態碼:'+${status}"></title>
</head>
<body>
<h2>500</h2>
<h1 th:text="'時間:'+${timestamp}"></h1>
<h1 th:text="'錯誤提示:'+${error}"></h1>
<h1 th:text="'異常對象:'+${exception}"></h1>
<h1 th:text="'異常信息:'+${message}"></h1>
</body>
</html>
咱們請求一個錯誤的地址路徑

咱們在程序代碼中人爲製造一個異常,請求響應

上面是有模版引擎的狀況下處理錯誤以及異常的方式,

若是項目中沒有模板引擎,(模板引擎找不到這個錯誤頁面),靜態資源文件夾static下找對應的4xx或者5xx或者更精確的錯誤頁面。可是若是不用模板引擎,頁面不能獲取上面說的頁面信息;

上面兩種方式使用手機訪問返回都是boot默認的json數據

第二種:pc端返回動態的頁面 ,手機端返回動態json數據

上面第一種能夠輕鬆的的處理異常,只需在指定的路徑下放靜態頁面(無模版引擎的狀況)或者攜帶相關信息的頁面(有模版引擎),

缺點就是不能在頁面攜帶咱們想要展現的數據,好比當咱們程序某處放生異常,咱們要返回咱們本身提示的錯誤信息。這種異常若是處理呢?

默認狀況下,在 Spring Boot 中,全部的異常數據其實就是第一種所展現出來的 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 中對異常數據的處理已經完成,開發者能夠直接使用。

package com.javayihao.top.config;

import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.WebRequest;

import java.util.Map;

@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", "服務器內部錯誤!");
        }
        if ((Integer)map.get("status") == 404) {
            map.put("message", "路徑不存在!");
        }
        return map;
    }
}

咱們服務器訪問 錯誤路徑

客戶端響應

訪問有異常的的控制器

客戶端響應

固然上面我能夠在程序任意位置拋出異常,使用全局異常處理器處理

自定義異常

全局異常處理器

@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(MyException.class)
    public String jsonErrorHandler(HttpServletRequest request, Exception e) {
        Map<String, Object> map = new HashMap<>();
        request.setAttribute("java.servlet.error.status_code", 500);
        map.put("code", -1);
        map.put("msg", e.getMessage());
        request.setAttribute("ext", map);
        //轉發到error
        return "forward:/error";
    }
}

 自定義ErrorAttributes

@Component
public class MyErrorAttributes extends DefaultErrorAttributes {
    //返回的map就是頁面或者json能獲取的全部字段
    @Override
    public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
        Map<String, Object> map = super.getErrorAttributes(webRequest, includeStackTrace);
        //能夠額外添加內容
        map.put("company", "javayihao");
        //取出異常處理器中的攜帶的數據
        Map<String, Object> ext = (Map<String, Object>) webRequest.getAttribute("ext", 0);//傳入0表明從request中獲取
        map.put("ext", ext);
        return map;
    }
}

 

第三種:pc端返回動態json數據,手機端也返回動態json數據

這種方式主要是針對先後端分離的項目,咱們自定義一個異常,在程序中用於拋出

定義一個返回結果對象(也能夠不用定義,直接使用map)存儲異常信息

定義一個全局異常處理器

/*ControllerAdvice用來配置須要進行異常處理的包和註解類型,
 好比@ControllerAdvice(annotations = RestController.class)
 只有類標有rescontrolle纔會被攔截
 */
@ControllerAdvice
public class MyExceptionHandler {
    //本身建立的異常按照本身編寫的信息返回便可
    @ExceptionHandler(value = MyException.class)
    @ResponseBody
    public ErrorInfo<String> errorInfo(HttpServletRequest req, MyException e) {
        ErrorInfo<String> r = new ErrorInfo<>();
        r.setCode(ErrorInfo.ERROR);
        r.setMessage(e.getMessage());
        r.setData("測試數據");
        r.setUrl(req.getRequestURL().toString());
        return r;
    }
    //系統異常時返回的異常編號爲 -1 ,返回的異常信息爲 「系統正在維護」;不能將原始的異常信息返回
    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ErrorInfo<String> errorInfo(HttpServletRequest req, Exception e) {
        ErrorInfo<String> r = new ErrorInfo<>();
        r.setCode(ErrorInfo.ERROR);
        r.setMessage("系統維護中");
        return r;
    }
}

相關文章
相關標籤/搜索