Spring Boot 錯誤處理機制源碼分析

程序員社區 2022-01-07 14:09:28 阅读数:21

spring boot 分析

       使用 Sping Boot 開發過程中, 我們經常會遇到 404,500 等錯誤,那麼 Spring Boot 對於出現的錯誤,又是怎麼個處理流程呢?

1.前情回顧

       ①當我們使用瀏覽器發送一個不存在的localhost:8081/users請求,出現 404 錯誤時,Spring Boot 解析按成之後會為我們返回一個默認的 Html 頁面提示
在這裏插入圖片描述
       ②我們不止使用瀏覽器訪問接口,當我們使用客戶端來發送相同請求時,此時客戶端為我們返回的卻是一個 Json 數據。這是為什麼呢? (接下來我們就帶著這個問題來分析 Spring Boot 錯誤處理機制)

在這裏插入圖片描述

2.Spring Boot 錯誤處理源碼分析時序圖

  如需原圖原圖,請點擊本鏈接下載(提取碼:dikj)
在這裏插入圖片描述

3.錯誤處理源碼分析

1.先來找錯誤原理入口

        先來找入口。我們知道錯誤信息其實是由Spring MVC 來解析的,在 Spring Boot 自動配置spring-boot-autoconfigure源碼包下,我們直接從 mvc 出入手,發現有以下幾個類就是用來處理我們的錯誤請求的。顯然ErrorMvcAutoConfiguration類就是錯誤處理的自動配置類。
在這裏插入圖片描述

2.ErrorMvcAutoConfiguration類分析

Ⅰ.我們先來分析錯誤處理自動配置類為我們自動配置了哪些重要的組件?(共4個)

①DefaultErrorAttributes 組件 容器中沒有 ErrorAttributes 組件時才會自動配置

@Bean@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)public DefaultErrorAttributes errorAttributes() {
 return new DefaultErrorAttributes();}

②BasicErrorController 組件 容器中沒有 ErrorController組件時才會自動配置

@Bean@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)public BasicErrorController basicErrorController(ErrorAttributes errorAttributes) {
 return new BasicErrorController(errorAttributes, this.serverProperties.getError(), this.errorViewResolvers);}

③ErrorPageCustomizer 組件

@Beanpublic ErrorPageCustomizer errorPageCustomizer() {
 return new ErrorPageCustomizer(this.serverProperties);}

④DefaultErrorViewResolver 組件

@Bean@ConditionalOnBean(DispatcherServlet.class)@ConditionalOnMissingBeanpublic DefaultErrorViewResolver conventionErrorViewResolver() {
 return new DefaultErrorViewResolver(this.applicationContext,this.resourceProperties);}

Ⅱ.當系統出現 4xx 或者 5xx之類的錯誤;ErrorPageCustomizer組件就會生效(來定制錯誤的響應規則),此時就會來到 /error 請求;該請求會被 BasicErrorController 處理;

@Overridepublic void registerErrorPages(ErrorPageRegistry errorPageRegistry) {
 //系統出現錯誤後,會來到error請求進行處理 ErrorPage errorPage = new ErrorPage(this.properties.getServletPrefix()+ this.properties.getError().getPath()); errorPageRegistry.addErrorPages(errorPage);}@Value("${error.path:/error}")//${error.path:/error} 會去配置文件獲取配置的路徑,如果沒有配置就走"/error"請求private String path = "/error";//getError().getPath()最終獲取到的是"error"請求

Ⅲ.發生錯誤後,BasicErrorController 就起作用了,用來處理"/error"錯誤請求

  以下代碼就是頁面訪問和客戶端訪問返回不同結果的原因。一個產生 html 數據、一個產生 json 數據。Spring Boot 是通過發送請求時請求頭中的"accept"參數來區別瀏覽器和客戶端的

@Controller//該組件就是一個普通 Controller@RequestMapping("${server.error.path:${error.path:/error}}")public class BasicErrorController extends AbstractErrorController {
 //1.產生 html 類型的數據,瀏覽器發送的請求就會來到這個方法處理 @RequestMapping(produces = "text/html") public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse response) {
 HttpStatus status = getStatus(request); //(此處預留,後面講)此處調用 getErrorAttributes() 方法,用來獲取錯誤信息 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); } //2.產生json數據,其他客戶端來到這個方法處理; @RequestMapping @ResponseBody 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<Map<String, Object>>(body, status); }}

此處分開來分析:

①先來分析:響應頁面 HTML 請求

 1. 來分析解析錯誤頁面 resolveErrorView()方法代碼

protected ModelAndView resolveErrorView(HttpServletRequest request,HttpServletResponse response, HttpStatus status, Map<String, Object> model) {
 //for循環解析所有的ErrorViewResolver,得到 ModelAndView 對象 for (ErrorViewResolver resolver : this.errorViewResolvers) {
 //調用 resolveErrorView 方法(此處調用的是 DefaultErrorViewResolver類中的resolveErrorView方法) //先看完第 2 步你就知道了為什麼。 ModelAndView modelAndView = resolver.resolveErrorView(request, status, model); if (modelAndView != null) {
 return modelAndView; } } return null;}

 2. 來分析ErrorViewResolver 類,我們發現該類是一個接口。它有一個唯一實現類DefaultErrorViewResolver類,該類就是我們開篇分析自動注入的四個重要組件之一
在這裏插入圖片描述
 3. 我們來分析DefaultErrorViewResolverresolveErrorView() 方法。你也可以直接去看簡單概括↓↓↓

public class DefaultErrorViewResolver implements ErrorViewResolver, Ordered {
 //使用靜態塊,定義异常 4xx、5xx,放入到 SERIES_VIEWS 中 static {
 Map<Series, String> views = new HashMap<Series, String>(); 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) {
 //(根據傳入的HttpStatus 狀態 和 參數)開始解析 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) {
 //默認 SpringBoot可以去找到一個頁面? error/404 String errorViewName = "error/" + viewName; //1.如果模板引擎可以解析這個頁面地址,就用模板引擎解析 TemplateAvailabilityProvider provider = this.templateAvailabilityProviders .getProvider(errorViewName, this.applicationContext); if (provider != null) {
 //模板引擎可用的情况下返回到errorViewName指定的視圖地址 return new ModelAndView(errorViewName, model); } //2.模板引擎不可用,就在靜態資源文件夾下找errorViewName對應的頁面 error/404.html return resolveResource(errorViewName, model); }}

簡單概括就是:

      ① 如果模板引擎可用,就使用模板引擎解析 /error/404/error/500等;(重要:如果使用的 Thymeleaf 模板引擎,直接在 /template 文件加下創建一個 /error/404.html 即可被解析,模板引擎會自動為你拼接前綴+後綴

      ② 如果模板引擎不可用,就去靜態資源文件夾下找對應的 /error/404.html頁面。(靜態資源文件共有5個,可參考:Spring Boot 對 js、css 等靜態資源的映射規則,將 /error/404.html 頁面放在這 5 個文件夾下即可)

      ③以上兩種情况都沒有 error 頁面,此時就會執行 Spring Boot 默認的錯誤頁面,new ModelAndView(“error”, model)返回 error 視圖,這個 error 視圖就是 Spring Boot 為我們配置的默認頁面。如下圖所示:
在這裏插入圖片描述

       重點:靜態塊中定義了 4xx 和5xx 兩個常量值,所以我們也可以使用 4xx 和 5xx 作為錯誤頁面的文件名來匹配這種類型的所有錯誤,精確優先(優先尋找精確的狀態碼.html,404錯誤,如果有404.html 就優先匹配,沒有才匹配4xx.html);

 4. 此處來分析getErrorAttributes()方法 (此處會涉及到開篇提及的最後一個還沒用到的組件:DefaultErrorAttributes)

protected Map<String, Object> getErrorAttributes(HttpServletRequest request,boolean includeStackTrace) {
 RequestAttributes requestAttributes = new ServletRequestAttributes(request); //errorAttributes是一個接口 //此處調用的是該接口的唯一實現類 DefaultErrorAttributes類中的 getErrorAttributes() return this.errorAttributes.getErrorAttributes(requestAttributes,includeStackTrace);}//具體實現@Overridepublic Map<String, Object> getErrorAttributes(RequestAttributes requestAttributes,boolean includeStackTrace) {
 Map<String, Object> errorAttributes = new LinkedHashMap<String, Object>(); errorAttributes.put("timestamp", new Date()); addStatus(errorAttributes, requestAttributes); addErrorDetails(errorAttributes, requestAttributes, includeStackTrace); addPath(errorAttributes, requestAttributes); return errorAttributes;}

     DefaultErrorAttributes 組件主要幫我們在頁面之間共享信息;在DefaultErrorAttributes類中,我們一共能够獲取到如下所有信息:

  1. timestamp:時間戳
  2. status:狀態碼
  3. error:錯誤提示
  4. exception:异常對象
  5. message:异常消息
  6. errors:JSR303數據校驗的錯誤都在這裏

這些參數,我們就可以直接拿來使用,如下:
在這裏插入圖片描述

②再來分析:響應定制的 Json 請求

  1. 同樣通過getErrorAttributes() 方法,返回异常相關信息;
  2. 調用 getStatus() 方法,通過request.getAttribute(“javax.servlet.error.status_code”);的方式獲取狀態值;
  3. 將錯誤信息封裝成一個 ResponseEntity 類,並以 Json 格式數據返回。

博主寫作不易,來個關注唄

求關注、求點贊,加個關注不迷路 ヾ(◍°∇°◍)ノ゙

博主不能保證寫的所有知識點都正確,但是能保證純手敲,錯誤也請指出,望輕噴 Thanks*(・ω・)ノ

版权声明:本文为[程序員社區]所创,转载请带上原文链接,感谢。 https://gsmany.com/2022/01/202201071409281934.html