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

SpringBoot錯誤處理流程深入詳解

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

一、錯誤處理

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

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

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

另外,templates下面error文件夾中的4xx,5xx頁面會被自動解析

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

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

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

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

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

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

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

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

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

@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()));
}

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

聲明類型為DefaultErrorViewResolver,id為conventionErrorViewResolver的bean(管理錯誤視圖跳轉(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);
   }
}

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

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

@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;
   }
}

另外還聲明了一個靜態(tài)內(nèi)部類StaticView,這里面涉及錯誤視圖的渲染等相關(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的異常處理流程,我們寫一個demo進(jìn)行debug

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

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

然后放置一個錯誤頁面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>

然后我們開啟debug模式,發(fā)送請求

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

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

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

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

在調(diào)用DefaultErrorAttributes時,它會將錯誤中的信息放入request請求域中(我們后面模板引擎頁面解析會用到)

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

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

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

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

四、定制錯誤處理邏輯

1、自定義錯誤頁面

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

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

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

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

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);
       // 打印完錯誤信息后,返回登錄頁
       return "login";
   }
}

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

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

為什么沒有再走到5xx.html呢?

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

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

@ResponseStatus+自定義異常

首先我們自定義一個異常類

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

然后寫一個接口去拋出自定義異常

/**
* 測試報錯信息
* @return 跳轉(zhuǎn)錯誤頁面
*/
@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()方法開始看

當(dāng)我們拋出自定義異常時,由于前面@ControllerAdvice+@ExceptionHandler修飾的類沒有指定處理這個異常,所以循環(huán)走到下一個異常處理解析器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請求,入?yún)锧ResponseStatus注解的code和reason
        response.sendError(statusCode, resolvedReason);
    }
	// 返回一個modelAndView
    return new ModelAndView();
}

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

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

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

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

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

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

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)先級提到第一位,Order越小,優(yōu)先級越高,所以我們這里設(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)先級提到最高時,前面的那些異常處理解析器都會失效,這時我們的自定義異常處理解析器可以作為默認(rèn)的全局異常處理規(guī)則

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

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

相關(guān)文章

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

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

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

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

    這篇文章給大家?guī)砹藘煞NIntelliJ IDEA 進(jìn)行代碼對比的方法,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2018-01-01
  • SpringBoot上傳圖片的示例

    SpringBoot上傳圖片的示例

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

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

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

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

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

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

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

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

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

    圖文詳解Java線程和線程池

    下面小編就為大家?guī)硪黄斦凧ava的線程和線程池。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2021-11-11
  • MyBatis+MyBatisPlus中遇到的一些坑及解決

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

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

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

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

最新評論