欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

SpringBoot錯(cuò)誤處理流程深入詳解

 更新時(shí)間:2022年10月03日 11:16:22   作者:Decade0712  
在項(xiàng)目開(kāi)發(fā)中出現(xiàn)異常時(shí)很平常不過(guò)的事情,我們處理異常也有很多種方式。本文將詳細(xì)為大家講解SpringBoot實(shí)現(xiàn)異常處理幾種方法,感興趣的可以學(xué)習(xí)一下

一、錯(cuò)誤處理

默認(rèn)情況下,Spring Boot提供/error處理所有錯(cuò)誤的映射

對(duì)于機(jī)器客戶端(例如PostMan),它將生成JSON響應(yīng),其中包含錯(cuò)誤,HTTP狀態(tài)和異常消息的詳細(xì)信息(如果設(shè)置了攔截器,需要在請(qǐng)求頭中塞入Cookie相關(guān)參數(shù))

對(duì)于瀏覽器客戶端,響應(yīng)一個(gè)“ whitelabel”錯(cuò)誤視圖,以HTML格式呈現(xiàn)相同的數(shù)據(jù)

另外,templates下面error文件夾中的4xx,5xx頁(yè)面會(huì)被自動(dòng)解析

二、底層相關(guān)組件

那么Spring Boot是怎么實(shí)現(xiàn)上述的錯(cuò)誤頁(yè)相關(guān)功能的呢?

我們又要來(lái)找一下相關(guān)源碼進(jìn)行分析了

首先我們先了解一個(gè)概念:@Bean配置的類的默認(rèn)id是方法的名稱,但是我們可以通過(guò)value或者name給這個(gè)bean取別名,兩者不可同時(shí)使用

我們進(jìn)入ErrorMvcAutoConfiguration,看這個(gè)類名應(yīng)該是和錯(cuò)誤處理的自動(dòng)配置有關(guān),我們看下這個(gè)類做了什么

向容器中注冊(cè)類型為DefaultErrorAttributes,id為errorAttributes的bean(管理錯(cuò)誤信息,如果要自定義錯(cuò)誤頁(yè)面打印的字段,就自定義它),這個(gè)類實(shí)現(xiàn)了ErrorAttributes, HandlerExceptionResolver(異常處理解析器接口), Ordered三個(gè)接口

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

點(diǎn)進(jìn)去后發(fā)現(xiàn),這個(gè)類是和我們響應(yīng)頁(yè)面中的message、error等字段有關(guān)

向容器中注冊(cè)一個(gè)id為basicErrorController的控制器bean(管理錯(cuò)誤相應(yīng)邏輯,不想返回json或者錯(cuò)誤視圖,就自定義它)

@Bean
@ConditionalOnMissingBean(
    value = {ErrorController.class},
    search = SearchStrategy.CURRENT
)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, ObjectProvider<ErrorViewResolver> errorViewResolvers) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(), (List)errorViewResolvers.orderedStream().collect(Collectors.toList()));
}

這個(gè)控制器就和前面我們返回json或者錯(cuò)誤視圖有關(guān)

聲明類型為DefaultErrorViewResolver,id為conventionErrorViewResolver的bean(管理錯(cuò)誤視圖跳轉(zhuǎn)路徑,如果要改變跳轉(zhuǎn)路徑,就自定義它)

@Configuration(
   proxyBeanMethods = false
)
@EnableConfigurationProperties({WebProperties.class, WebMvcProperties.class})
static class DefaultErrorViewResolverConfiguration {
   private final ApplicationContext applicationContext;
   private final Resources resources;
   DefaultErrorViewResolverConfiguration(ApplicationContext applicationContext, WebProperties webProperties) {
       this.applicationContext = applicationContext;
       this.resources = webProperties.getResources();
   }
   @Bean
   @ConditionalOnBean({DispatcherServlet.class})
   @ConditionalOnMissingBean({ErrorViewResolver.class})
   DefaultErrorViewResolver conventionErrorViewResolver() {
       return new DefaultErrorViewResolver(this.applicationContext, this.resources);
   }
}

這個(gè)類中,解釋了為什么前面會(huì)根據(jù)不同的狀態(tài)碼轉(zhuǎn)向不同的錯(cuò)誤頁(yè)

聲明一個(gè)靜態(tài)內(nèi)部類WhitelabelErrorViewConfiguration,它與錯(cuò)誤視圖配置相關(guān),這個(gè)類中聲明了一個(gè)id為error的視圖對(duì)象提供給basicErrorController中使用,還定義了視圖解析器BeanNameViewResolver ,它會(huì)根據(jù)返回的視圖名作為組件的id去容器中找View對(duì)象

@Configuration(
   proxyBeanMethods = false
)
@ConditionalOnProperty(
   prefix = "server.error.whitelabel",
   name = {"enabled"},
   matchIfMissing = true
)
@Conditional({ErrorMvcAutoConfiguration.ErrorTemplateMissingCondition.class})
protected static class WhitelabelErrorViewConfiguration {
   private final ErrorMvcAutoConfiguration.StaticView defaultErrorView = new ErrorMvcAutoConfiguration.StaticView();
   protected WhitelabelErrorViewConfiguration() {
   }
   @Bean(
       name = {"error"}
   )
   @ConditionalOnMissingBean(
       name = {"error"}
   )
   public View defaultErrorView() {
       return this.defaultErrorView;
   }
   @Bean
   @ConditionalOnMissingBean
   public BeanNameViewResolver beanNameViewResolver() {
       BeanNameViewResolver resolver = new BeanNameViewResolver();
       resolver.setOrder(2147483637);
       return resolver;
   }
}

另外還聲明了一個(gè)靜態(tài)內(nèi)部類StaticView,這里面涉及錯(cuò)誤視圖的渲染等相關(guān)操作

private static class StaticView implements View {
   private static final MediaType TEXT_HTML_UTF8;
   private static final Log logger;
   private StaticView() {
   }
   public void render(Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception {
       if (response.isCommitted()) {
           String message = this.getMessage(model);
           logger.error(message);
       } else {
           response.setContentType(TEXT_HTML_UTF8.toString());
           StringBuilder builder = new StringBuilder();
           Object timestamp = model.get("timestamp");
           Object message = model.get("message");
           Object trace = model.get("trace");
           if (response.getContentType() == null) {
               response.setContentType(this.getContentType());
           }
           ...

三、異常處理流程

為了了解Spring Boot的異常處理流程,我們寫一個(gè)demo進(jìn)行debug

首先寫一個(gè)會(huì)發(fā)生算術(shù)運(yùn)算異常的接口/test_error

/**
 * 測(cè)試報(bào)錯(cuò)信息
 * @return 跳轉(zhuǎn)錯(cuò)誤頁(yè)面
 */
@GetMapping(value = "/test_error")
public String testError() {
    int a = 1/0;
    return String.valueOf(a);
}

然后放置一個(gè)錯(cuò)誤頁(yè)面5xx.html于templates下的error文件夾中

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">
  <meta name="description" content="">
  <meta name="author" content="ThemeBucket">
  <link rel="shortcut icon" href="#" rel="external nofollow"  rel="external nofollow"  type="image/png">
  <title>500 Page</title>
  <link href="css/style.css" rel="external nofollow"  rel="stylesheet">
  <link href="css/style-responsive.css" rel="external nofollow"  rel="stylesheet">
  <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
  <!--[if lt IE 9]>
  <script src="js/html5shiv.js"></script>
  <script src="js/respond.min.js"></script>
  <![endif]-->
</head>
<body class="error-page">
<section>
    <div class="container ">
        <section class="error-wrapper text-center">
            <h1><img alt="" src="images/500-error.png"></h1>
            <h2>OOOPS!!!</h2>
            <h3 th:text="${message}">Something went wrong.</h3>
            <p class="nrml-txt" th:text="${trace}">Why not try refreshing you page? Or you can <a href="#" rel="external nofollow"  rel="external nofollow" >contact our support</a> if the problem persists.</p>
            <a class="back-btn" href="index.html" rel="external nofollow"  th:text="${status}"> Back To Home</a>
        </section>
    </div>
</section>
<!-- Placed js at the end of the document so the pages load faster -->
<script src="js/jquery-1.10.2.min.js"></script>
<script src="js/jquery-migrate-1.2.1.min.js"></script>
<script src="js/bootstrap.min.js"></script>
<script src="js/modernizr.min.js"></script>
<!--common scripts for all pages-->
<!--<script src="js/scripts.js"></script>-->
</body>
</html>

然后我們開(kāi)啟debug模式,發(fā)送請(qǐng)求

首先,我們的斷點(diǎn)還是來(lái)到DispatcherServlet類下的doDispatch()方法

經(jīng)過(guò)mv = ha.handle(processedRequest, response, mappedHandler.getHandler());調(diào)用目標(biāo)方法之后,他會(huì)返回相關(guān)錯(cuò)誤信息,并將其塞入dispatchException這個(gè)對(duì)象

然后調(diào)用this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);處理調(diào)度結(jié)果

然后他會(huì)在processDispatchResult()中經(jīng)過(guò)判斷是否存在異常,異常不為空,調(diào)用processHandlerException()方法,這里它會(huì)遍歷系統(tǒng)中所有的異常處理解析器,哪個(gè)解析器返回結(jié)果不為null,就結(jié)束循環(huán)

在調(diào)用DefaultErrorAttributes時(shí),它會(huì)將錯(cuò)誤中的信息放入request請(qǐng)求域中(我們后面模板引擎頁(yè)面解析會(huì)用到)

遍歷完所有解析器,我們發(fā)現(xiàn)他們都不能返回一個(gè)不為空的ModelAndView對(duì)象,于是它會(huì)繼續(xù)拋出異常

當(dāng)系統(tǒng)發(fā)現(xiàn)沒(méi)有任何人能處理這個(gè)異常時(shí),底層就會(huì)發(fā)送 /error 請(qǐng)求,它就會(huì)被我們上面介紹的BasicErrorController下的errorHtml()方法處理

這個(gè)方法會(huì)通過(guò)ModelAndView modelAndView = this.resolveErrorView(request, response, status, model);去遍歷系統(tǒng)中所有的錯(cuò)誤視圖解析器,如果調(diào)用解析器的resolveErrorView()方法返回結(jié)果不為空就結(jié)束循環(huán)

系統(tǒng)中只默認(rèn)注冊(cè)了一個(gè)錯(cuò)誤視圖解析器,也就是我們上面介紹的DefaultErrorViewResolver,跟隨debug斷點(diǎn)我們得知,這個(gè)解析器會(huì)把error+響應(yīng)狀態(tài)碼作為錯(cuò)誤頁(yè)的地址,最終返回給我們的視圖地址為error/5xx.html

四、定制錯(cuò)誤處理邏輯

1、自定義錯(cuò)誤頁(yè)面

error下的4xx.html和5xx.html,根據(jù)我們上面了解的DefaultErrorViewResolver類可以,它的resolveErrorView()方法在進(jìn)行錯(cuò)誤頁(yè)解析時(shí),如果有精確的錯(cuò)誤狀態(tài)碼頁(yè)面就匹配精確,沒(méi)有就找 4xx.html,如果都沒(méi)有就轉(zhuǎn)到系統(tǒng)默認(rèn)的錯(cuò)誤頁(yè)

2、使用注解或者默認(rèn)的異常處理

@ControllerAdvice+@ExceptionHandler處理全局異常,我們結(jié)合一個(gè)demo來(lái)了解一下用法

首先我們創(chuàng)建一個(gè)類用來(lái)處理全局異常

package com.decade.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
@Slf4j
public class MyExceptionHandler {
   // 指定該方法處理某些指定異常,@ExceptionHandler的value可以是數(shù)組,這里我們指定該方法處理數(shù)學(xué)運(yùn)算異常和空指針異常
   @ExceptionHandler(value = {ArithmeticException.class, NullPointerException.class})
   public String handleArithmeticException(Exception exception) {
       log.error("異常信息為:{}", exception);
       // 打印完錯(cuò)誤信息后,返回登錄頁(yè)
       return "login";
   }
}

我們還是使用上面的會(huì)發(fā)生算術(shù)運(yùn)算異常的接口/test_error進(jìn)行測(cè)試

請(qǐng)求接口后發(fā)現(xiàn),頁(yè)面跳轉(zhuǎn)到登錄頁(yè)了

為什么沒(méi)有再走到5xx.html呢?

因?yàn)锧ControllerAdvice+@ExceptionHandler的底層是ExceptionHandlerExceptionResolver來(lái)處理的

這樣在進(jìn)入DispatcherServlet類下的processHandlerException()方法時(shí),就會(huì)調(diào)用ExceptionHandlerExceptionResolver這個(gè)異常處理解析器,從而跳轉(zhuǎn)到我們自己創(chuàng)建的異常處理類進(jìn)行異常處理,然后返回不為null的ModelAndView對(duì)象給它,終止遍歷,不會(huì)再發(fā)送/error請(qǐng)求

@ResponseStatus+自定義異常

首先我們自定義一個(gè)異常類

package com.decade.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
// code對(duì)應(yīng)錯(cuò)誤碼,reason對(duì)應(yīng)message
@ResponseStatus(code = HttpStatus.METHOD_NOT_ALLOWED, reason = "自定義異常")
public class CustomException extends RuntimeException {
   public CustomException() {
   }
   public CustomException(String message) {
       super(message);
   }
}

然后寫一個(gè)接口去拋出自定義異常

/**
* 測(cè)試報(bào)錯(cuò)信息
* @return 跳轉(zhuǎn)錯(cuò)誤頁(yè)面
*/
@GetMapping(value = "/test_responseStatus")
public String testResponseStatus(@RequestParam("param") String param) {
   if ("test_responseStatus".equals(param)) {
       throw new CustomException();
   }
   return "main";
}

最后我們調(diào)用接口,可以得到,跳轉(zhuǎn)到了4xx.html,但是狀態(tài)碼和message都和我們自己定義的匹配

那么原理是什么呢?我們還是從DispatcherServlet類下的processHandlerException()方法開(kāi)始看

當(dāng)我們拋出自定義異常時(shí),由于前面@ControllerAdvice+@ExceptionHandler修飾的類沒(méi)有指定處理這個(gè)異常,所以循環(huán)走到下一個(gè)異常處理解析器ResponseStatusExceptionResolver

我們分析一下這里的代碼

@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    try {
        if (ex instanceof ResponseStatusException) {
            return this.resolveResponseStatusException((ResponseStatusException)ex, request, response, handler);
        }
		// 由于我們自定義異常類使用了@ResponseStatus注解修飾,所以我們這里獲取到的status信息不為空
        ResponseStatus status = (ResponseStatus)AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
        if (status != null) {
            return this.resolveResponseStatus(status, request, response, handler, ex);
        }
		...
protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
    // 獲取@ResponseStatus注解的code和reason作為狀態(tài)碼和message
	int statusCode = responseStatus.code().value();
    String reason = responseStatus.reason();
    return this.applyStatusAndReason(statusCode, reason, response);
}
protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) throws IOException {
    if (!StringUtils.hasLength(reason)) {
        response.sendError(statusCode);
    } else {
        String resolvedReason = this.messageSource != null ? this.messageSource.getMessage(reason, (Object[])null, reason, LocaleContextHolder.getLocale()) : reason;
		// 發(fā)送/error請(qǐng)求,入?yún)锧ResponseStatus注解的code和reason
        response.sendError(statusCode, resolvedReason);
    }
	// 返回一個(gè)modelAndView
    return new ModelAndView();
}

經(jīng)過(guò)debug我們知道,ResponseStatusExceptionResolver這個(gè)異常處理解析器返回了一個(gè)空的ModelAndView對(duì)象給我們,而且還通過(guò)response.sendError(statusCode, resolvedReason);發(fā)送了/error請(qǐng)求

這樣就又走到了上面的第三節(jié)處理/error請(qǐng)求的流程中,從而帶著我們@ResponseStatus注解的code和reason跳轉(zhuǎn)到了4xx.html頁(yè)面,這樣就能解釋為什么4xx.html頁(yè)面中的狀態(tài)碼和message都是我們自定義的了

如果沒(méi)有使用上述2種方法處理指定異?;蛱幚砦覀冏约鹤远x的異常,那么系統(tǒng)就會(huì)按照Spring底層的異常進(jìn)行處理,如 請(qǐng)求方法不支持異常等,都是使用DefaultHandlerExceptionResolver這個(gè)異常處理解析器進(jìn)行處理的

我們分析這個(gè)類的doResolveException()方法得知,它最后也會(huì)發(fā)送/error請(qǐng)求,從而轉(zhuǎn)到4xx.html或者5xx.html頁(yè)面

3、自定義異常處理解析器

使用@Component注解,并實(shí)現(xiàn)HandlerExceptionResolver接口來(lái)自定義一個(gè)異常處理解析器

package com.decade.exception;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
// 將優(yōu)先級(jí)提到第一位,Order越小,優(yōu)先級(jí)越高,所以我們這里設(shè)置int的最小值
@Order(Integer.MIN_VALUE)
@Component
public class CustomExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        try {
            response.sendError(500, "自己定義的異常");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

當(dāng)我們把優(yōu)先級(jí)提到最高時(shí),前面的那些異常處理解析器都會(huì)失效,這時(shí)我們的自定義異常處理解析器可以作為默認(rèn)的全局異常處理規(guī)則

值得注意的是,當(dāng)代碼走到response.sendError時(shí),就會(huì)觸發(fā)/error請(qǐng)求,當(dāng)你的異常沒(méi)有人能處理時(shí),也會(huì)走tomcat底層觸發(fā)response.sendError,發(fā)送/error請(qǐng)求

到此這篇關(guān)于SpringBoot錯(cuò)誤處理流程深入詳解的文章就介紹到這了,更多相關(guān)SpringBoot錯(cuò)誤處理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解idea中web.xml默認(rèn)版本問(wèn)題解決

    詳解idea中web.xml默認(rèn)版本問(wèn)題解決

    這篇文章主要介紹了詳解idea中web.xml默認(rèn)版本問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-12-12
  • 使用IntelliJ IDEA 進(jìn)行代碼對(duì)比的方法(兩種方法)

    使用IntelliJ IDEA 進(jìn)行代碼對(duì)比的方法(兩種方法)

    這篇文章給大家?guī)?lái)了兩種IntelliJ IDEA 進(jìn)行代碼對(duì)比的方法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • SpringBoot上傳圖片的示例

    SpringBoot上傳圖片的示例

    這篇文章主要介紹了SpringBoot上傳圖片的示例,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下
    2020-11-11
  • Java中調(diào)用SQL Server存儲(chǔ)過(guò)程詳解

    Java中調(diào)用SQL Server存儲(chǔ)過(guò)程詳解

    這篇文章主要介紹了Java中調(diào)用SQL Server存儲(chǔ)過(guò)程詳解,本文講解了使用不帶參數(shù)的存儲(chǔ)過(guò)程、使用帶有輸入?yún)?shù)的存儲(chǔ)過(guò)程、使用帶有輸出參數(shù)的存儲(chǔ)過(guò)程、使用帶有返回狀態(tài)的存儲(chǔ)過(guò)程、使用帶有更新計(jì)數(shù)的存儲(chǔ)過(guò)程等操作實(shí)例,需要的朋友可以參考下
    2015-01-01
  • 詳解如何使用SpringBoot的緩存@Cacheable

    詳解如何使用SpringBoot的緩存@Cacheable

    這篇文章主要為大家介紹了如何使用SpringBoot的緩存@Cacheable詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • 淺析Spring中的循環(huán)依賴問(wèn)題

    淺析Spring中的循環(huán)依賴問(wèn)題

    這篇文章主要介紹了淺析Spring中的循環(huán)依賴問(wèn)題,Spring 是利用了 三級(jí)緩存 來(lái)解決循環(huán)依賴的,其實(shí)現(xiàn)本質(zhì)是通過(guò)提前暴露已經(jīng)實(shí)例化但尚未初始化的 bean 來(lái)完成的,需要的朋友可以參考下
    2023-11-11
  • Java實(shí)現(xiàn)PDF打印的解決方案

    Java實(shí)現(xiàn)PDF打印的解決方案

    今天小編就為大家分享一篇關(guān)于Java實(shí)現(xiàn)PDF打印的解決方案,小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • 圖文詳解Java線程和線程池

    圖文詳解Java線程和線程池

    下面小編就為大家?guī)?lái)一篇詳談Java的線程和線程池。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2021-11-11
  • MyBatis+MyBatisPlus中遇到的一些坑及解決

    MyBatis+MyBatisPlus中遇到的一些坑及解決

    這篇文章主要介紹了MyBatis+MyBatisPlus中遇到的一些坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • 使用Runtime 調(diào)用Process.waitfor導(dǎo)致的阻塞問(wèn)題

    使用Runtime 調(diào)用Process.waitfor導(dǎo)致的阻塞問(wèn)題

    這篇文章主要介紹了使用Runtime 調(diào)用Process.waitfor導(dǎo)致的阻塞問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12

最新評(píng)論