Spring Boot 全局異常處理(上)

背景

對接的項目多了,奇奇怪怪的問題就都出現了,好比有一個最讓人煩心的問題 異常html

偶爾會碰到框架拋出的默認的異常,好比 Laraval,好比 Spring Boot,每一個框架拋出的異常格式是不一致的,有 Json 或 XML 格式的數據,更甚至有 HTML 頁面,最爲關鍵的是響應的數據結構和接口約定的數據結構不一致,因此這時候咱們在對響應內容進行解析的時候反而會給咱們本身的代碼帶來須要處理的異常。web

基於此,爲了對本身的接口負責,咱們須要進行全局的異常處理,目的是防止出現約定以外的數據結構。spring

Spring Boot 默認的異常處理機制

默認狀況下,Spring Boot 會返回兩種類型的異常,一種是 HTML,還有一種是 Json 格式的數據,這主要取決於請求頭中的 Accept 參數,好比瀏覽器發出的請求,請求頭中會附帶 Accept:text/html,因此此時 Spring Boot 會返回一個錯誤頁面,稱爲 Whitelabel Error Page,而當咱們使用 Postman 請求時,返回的則是 Json 類型的數據。瀏覽器

原理其實也很簡單,Spring Boot 默認提供了程序出錯的結果映射路徑 /error。而這個 /error 請求會由 BasicErrorController 來處理,其內部其實就是經過判斷請求頭的 Accept 中的內容來進行區分處理邏輯的(判斷是否包含 text/html),從而來決定返回頁面視圖仍是 JSON 消息內容。springboot

相關 BasicErrorController 中代碼以下:數據結構

BasicErrorController 處理邏輯

自定義錯誤頁面

自定義錯誤頁面的好處有好多,好比 404 錯誤頁面,咱們徹底能夠自定義 404 的 HTML 頁面,上面能夠放置圖片等,這樣體驗就更友好一點。app

自定義的錯誤頁面有兩種,一種是 靜態頁面,一種是使用 模板引擎 動態生成,後者的優點是能夠在頁面上顯示自定義的內容。框架

  • 靜態頁面的方式,html 文件的路徑爲:resources/public/error/xxx.html

若是要替換 404 錯誤頁面,則在此路徑下放置 404.html 文件,同理,若是要替換 500 錯誤頁面,則在此路徑下放置 500.html 文件便可ide

  • 模板引擎渲染的動態頁面的方式,html 文件的路徑爲:resources/templates/error/xxx.html

文件命名同上spa

注意:動態頁面的優先級是要高於靜態頁面的 好比你同時配置了靜態頁面和動態頁面,那麼最終生效的,會是動態頁面。

附上文件結構圖:

文件結構

自定義錯誤信息

上面介紹了最簡單的錯誤處理,最主要的針對返回的 HTML,可是咱們每每也要處理 Json 類型的返回內容,目的是讓數據結構和咱們的接口返回的數據結構一致。

Step 1 自定義 servlet 容器

須要注意的是,Spring Boot 2.x 和 Sprig Boot 1.x 是不同的。

此處 Demo 咱們僅處理了 404 和 500 這兩種異常。

@Configuration
public class ContainerConfig {

    /** 下面是 springboot 2.x 系列的寫法 */
    @Bean
    public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
        return factory -> {
            factory.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
            factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
        };
    }

    /** 下面是 springboot 1.x 系列的寫法 */
    /*@Bean public EmbeddedServletContainerCustomizer containerCustomizer(){ return new EmbeddedServletContainerCustomizer(){ @Override public void customize(ConfigurableEmbeddedServletContainer container) { container.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500")); container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404")); } }; }*/
}
複製代碼

Step 2 自定義對應的請求處理類

@Controller
public class MyBasicErrorController extends BasicErrorController {

    public MyBasicErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }

    /** * @Description: 定義500的ModelAndView * @Param: [request, response] * @return: org.springframework.web.servlet.ModelAndView * @Author: Jet.Chen * @Date: 2019-07-17 21:56 */
    @RequestMapping(produces = "text/html",value = "/500")
    public ModelAndView errorHtml500(HttpServletRequest request, HttpServletResponse response) {
        response.setStatus(getStatus(request).value());
        Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("msg","自定義錯誤信息");
        return new ModelAndView("error/500", model);
    }


    /** * @Description: 定義500 和 404 的錯誤JSON信息 * @Param: [request] * @return: org.springframework.http.ResponseEntity<cn.jetchen.steecrserver.config.STCRResposeData> * STCRResposeData 爲全局統一的接口數據結構 * @Author: Jet.Chen * @Date: 2019-07-17 23:13 */
    @RequestMapping(value = {"/500", "/404"})
    @ResponseBody
    public ResponseEntity<STCRResposeData> error500(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        HttpStatus status = getStatus(request);
        Object messageTemp;
        // stcrResposeData 爲返回的數據
        STCRResposeData stcrResposeData = STCRResposeData.initError(
                String.format("%d%d", 1, status.value()), 
                (messageTemp = body.get("error")) == null ? null : messageTemp.toString(),
                new HashMap<String, Object>() {{
                    put("error", body.get("error"));
                    put("message", body.get("message"));
                }});
        return new ResponseEntity<>(stcrResposeData, status);
    }


    /** * @Description: 定義404的ModelAndView * @Param: [request, response] * @return: org.springframework.web.servlet.ModelAndView * @Author: Jet.Chen * @Date: 2019-07-17 23:13 */
    @RequestMapping(produces = "text/html",value = "/404")
    public ModelAndView errorHtml400(HttpServletRequest request, HttpServletResponse response) {
        response.setStatus(getStatus(request).value());
        Map<String, Object> model = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML));
        model.put("msg","自定義錯誤信息");
        return new ModelAndView("error/404", model);
    }
}
複製代碼

小結

全局異常的處理是很是有必要的,可是此文到此的處理方式其實才完成了一半,這些會在下篇文章中介紹,主要是由於此到處理的是全局的錯誤,是在過濾器以外的,可是咱們但願處理的粒度更細一點。

好比在控制器層的全局處理方式:@ControllerAdvice,從抽象概念上能夠理解成它是處理那些在 Controller 方法中拋出的異常。

image
相關文章
相關標籤/搜索