日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

Spring Boot Spring MVC异常处理原理分析

發(fā)布時間:2025/3/15 javascript 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Spring Boot Spring MVC异常处理原理分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

一、Spring MVC為處理異常的前期準(zhǔn)備

  • DispatcherServlet
    入口類,是一個Servlet,是所有請求的分發(fā)點
    • 初始化

    DispatcherServlet在初始化時會觸發(fā)onRefresh()方法,此方法會調(diào)用initStrategies方法(初始化九大組件),完成整個DispatcherServlet的初始化工作,其中initHandlerExceptionResolvers()會初始化HandlerExceptionResolvers對象

    protected void initStrategies(ApplicationContext context) {initMultipartResolver(context);initLocaleResolver(context);initThemeResolver(context);initHandlerMappings(context);initHandlerAdapters(context);// 初始化HandlerExceptionResolvers對象initHandlerExceptionResolvers(context);initRequestToViewNameTranslator(context);initViewResolvers(context);initFlashMapManager(context); }

    從Spring容器的ApplicationContext中找出所有HandlerExceptionResolvers對象,將保存到對象屬性handlerExceptionResolvers 中。從這我們也知道如果要在spring mvc中插入自己的HandlerExceptionResolver也比較簡單,只需要類實現(xiàn)接口HandlerExceptionResolver和Ordered,使用類似@Component 的注解注解此類即可

    private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// 從Spring容器的ApplicationContext中找出所有HandlerExceptionResolvers對象Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {// 將Map轉(zhuǎn)化為List,保存到屬性handlerExceptionResolvers 中this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());// 對HandlerExceptionResolvers使用據(jù)order接口里值進(jìn)行排序AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);} } ...

    到此初始化工作完成

  • 關(guān)于HandlerExceptionResolver(異常解析器)以及常用的實現(xiàn)類
    • HandlerExceptionResolver接口
      HandlerExceptionResolver是一個接口,用于處理網(wǎng)絡(luò)請求過程中拋出的異常,但是不處理異常本身拋出的異常和視圖解析過程中拋出的異常

    下圖是Spring MVC默認(rèn)實現(xiàn)的HandlerExceptionResolver類

    • HandlerExceptionResolverComposite
      Spring Boot啟動時會默認(rèn)注冊HandlerExceptionResolverComposite對象。此類只是一個組合類,并不進(jìn)行真正的異常處理。當(dāng)他捕獲異常時他只是將異常輪詢委托給注冊到它屬性里的上的HandlerExceptionResolver類來處理異常,如果處理的結(jié)果不為null,則轉(zhuǎn)給下一個處理
    @Override public ModelAndView resolveException(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex) {if (resolvers != null) {for (HandlerExceptionResolver handlerExceptionResolver : resolvers) {ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);if (mav != null) {return mav;}}}return null; }

    默認(rèn)注冊到HandlerExceptionResolverComposite 的屬性有以下3個HandlerExceptionResolver,按照優(yōu)先級排列如下:

    • ExceptionHandlerExceptionResolver
    • ResponseStatusExceptionResolver
    • DefaultHandlerExceptionResolver
    • Spring mvc啟動時,初始化所有HandlerExceptionResolver到Spring 容器中,在Spring boot在啟動時,會初始化WebMvcConfigurationSupport 里配置的Bean, 會創(chuàng)建HandlerExceptionResolverComposite對象,此對象包括3個HandlerExceptionResolver,當(dāng)他捕獲異常時,會使用這3個HandlerExceptionResolver進(jìn)行處理,詳細(xì)如下:
    public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {@Beanpublic HandlerExceptionResolver handlerExceptionResolver() {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {// 添加默認(rèn)HandlerExceptionResolver類 addDefaultHandlerExceptionResolvers(exceptionResolvers);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;}// 添加默認(rèn)HandlerExceptionResolverComposite及注冊到此對象中的HandlerExceptionResolverCompositeprotected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {// 創(chuàng)建 ExceptionHandlerExceptionResolver()ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager());exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.<ResponseBodyAdvice<?>>singletonList(new JsonViewResponseBodyAdvice()));}exceptionHandlerResolver.setApplicationContext(this.applicationContext);exceptionHandlerResolver.afterPropertiesSet();exceptionResolvers.add(exceptionHandlerResolver);// 創(chuàng)建ResponseStatusExceptionResolver ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);exceptionResolvers.add(responseStatusResolver);// 創(chuàng)建DefaultHandlerExceptionResolverexceptionResolvers.add(new DefaultHandlerExceptionResolver());}protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {return new ExceptionHandlerExceptionResolver();} }

    二、詳細(xì)介紹這3個HandlerExceptionResolver的作用

  • ExceptionHandlerExceptionResolver
    使用@ExceptionHandler注解方法處理異常類,使用注解處理異常就有這個類的功勞。默認(rèn)情況下,這個HandlerExceptionResolver的優(yōu)先級是最高。
    以下是ExceptionHandlerExceptionResolver運(yùn)行時屬性值
    • 屬性exceptionHandlerAdviceCache :存儲@Controller里@ExceptionHandler的方法
    • 屬性exceptionHandlerAdviceCache:存儲@ControllerAdvice里@ExceptionHandler的全局方法

    處理異常的關(guān)鍵代碼
    入口doResolveHandlerMethodException方法會通過 getExceptionHandlerMethod獲取對應(yīng)的@ExceptionHandler方法,如果有找到則執(zhí)行此方法

    @Overrideprotected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {// 杳找對應(yīng)的方法@ExceptionHandlerServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;} ....if (cause != null) {// 執(zhí)行異常處理方法exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);}else {// 執(zhí)行異常處理方法exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);}} ....}
  • getExceptionHandlerMethod方法:查找特定異常的@ExceptionHandler方法,首先從拋出異常的@Controller類中尋找對應(yīng)的處理方法,如果沒有再從@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,則調(diào)用這個方法執(zhí)行處理,否則返回null
  • protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);if (handlerMethod != null) {// 從拋出異常的@Controller類中自身中尋找對應(yīng)的處理方法,如果有找到先緩存ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}Method method = resolver.resolveMethod(exception);if (method != null) {// 調(diào)用方法,返回結(jié)果return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);}}// 如果沒有再從@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,則調(diào)用這個方法執(zhí)行處理for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {if (entry.getKey().isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);}}}return null; }
  • ResponseStatusExceptionResolver
    使用@ResponseStatus處理異常,將異常轉(zhuǎn)化對應(yīng)的HTTP的狀態(tài)碼。@ResponseStatus可以定義在Excpetion的子類的類上,也可以定義在被@ExceptionHandler注解的方法上(不過這個需要小心使用,由于ExceptionHandlerExceptionResolver的優(yōu)先級高,這種方式可能被ExceptionHandlerExceptionResolver覆蓋掉)
  • 異常處理入口doResolveException方法會先查找異常上的@ResponseStatus注解信息,如果有ResponseStatus ,則按照ResponseStatus 配置的值處理

    // protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) {// 獲取異常的@ResponseStatus注解信息ResponseStatus responseStatus = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);if (responseStatus != null) {try {// 如果有ResponseStatus ,則按照ResponseStatus 配置的值處理return resolveResponseStatus(responseStatus, request, response, handler, ex);}catch (Exception resolveEx) {logger.warn("Handling of @ResponseStatus resulted in Exception", resolveEx);}}else if (ex.getCause() instanceof Exception) {}return null; }

    根據(jù)ResponseStatus 的值設(shè)置返回的http狀態(tài)碼和原因

    protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request,HttpServletResponse response, Object handler, Exception ex) throws Exception {int statusCode = responseStatus.code().value();String reason = responseStatus.reason();if (!StringUtils.hasLength(reason)) {// 設(shè)置返回的http狀態(tài)碼response.sendError(statusCode);}else {String resolvedReason = (this.messageSource != null ?this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :reason);// 設(shè)置返回的http狀態(tài)碼和原因response.sendError(statusCode, resolvedReason);}return new ModelAndView();}
  • DefaultHandlerExceptionResolver
    默認(rèn)的HandlerExceptionResolver,將特定異常轉(zhuǎn)化為標(biāo)準(zhǔn)的HTTP的狀態(tài)碼。
    詳細(xì)如下:左邊是異常名稱,右邊是http的狀態(tài)碼
  • 通過代碼解釋此類行為, 只列出NoSuchRequestHandlingMethodException相關(guān)的轉(zhuǎn)換http錯誤碼的代碼,表格里其他異常處理類似

    異常處理入口doResolveException方法,如果發(fā)現(xiàn)異常是NoSuchRequestHandlingMethodException,則調(diào)用方法handleNoSuchRequestHandlingMethod

    // 對于NoSuchRequestHandlingMethodException進(jìn)行轉(zhuǎn)化http錯誤大碼 protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) {try {if (ex instanceof org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) {return handleNoSuchRequestHandlingMethod((org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException) ex,request, response, handler);}else if (ex instanceof HttpRequestMethodNotSupportedException) {}else if}

    handleNoSuchRequestHandlingMethod方法返回404錯誤碼和錯誤信息

    protected ModelAndView handleNoSuchRequestHandlingMethod(org.springframework.web.servlet.mvc.multiaction.NoSuchRequestHandlingMethodException ex,HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {pageNotFoundLogger.warn(ex.getMessage());response.sendError(HttpServletResponse.SC_NOT_FOUND);return new ModelAndView();}
  • Ordered和實現(xiàn)自定義HandlerExceptionResolver類
    每個具體的HandlerExceptionResolver都會實現(xiàn)Ordered接口,來定義執(zhí)行的順序,order值越小,越是優(yōu)先執(zhí)行。
    如果要實現(xiàn)自己HandlerExceptionResolver,只需要滿足兩個條件:
    • 實現(xiàn)接口HandlerExceptionResolver和Ordered
    • 使用類似@Component 的注解注解此類,保證spring啟動時創(chuàng)建此類對應(yīng)的對象即可

    三、異常處理流程

    • 當(dāng)執(zhí)行@RequestMapping拋出異常,會進(jìn)入異常處理流程
    • 所有的doPost, doGet等do*的方法都會執(zhí)行到以下方法:找到真正業(yè)務(wù)的處理邏輯,并進(jìn)行處理。

    下面的代碼是找到本次請求真正要處理的HandlerAdapter 對象,并進(jìn)行處理,最后調(diào)用processDispatchResult對結(jié)果進(jìn)行處理,這是我們關(guān)心的內(nèi)容

    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null || mappedHandler.getHandler() == null) {noHandlerFound(processedRequest, response);return;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());// Process last-modified header, if supported by the handler.String method = request.getMethod();boolean isGet = "GET".equals(method);if (isGet || "HEAD".equals(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (logger.isDebugEnabled()) {logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);}if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {return;}applyDefaultViewName(processedRequest, mv);mappedHandler.applyPostHandle(processedRequest, response, mv);}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {// As of 4.3, we're processing Errors thrown from handler methods as well,// making them available for @ExceptionHandler methods and other scenarios.dispatchException = new NestedServletException("Handler dispatch failed", err);}// 處理最后的方法processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}}

    處理業(yè)務(wù)執(zhí)行的結(jié)果,處理結(jié)束可能是 ModelAndView,也可能是Exception。如果結(jié)果是Exception,就需要通過本文提到的HandlerExceptionResolver轉(zhuǎn)化為ModelAndView。然后根據(jù)ModelAndView將結(jié)果返回給請求方

    private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {// 如果返回值是異常,通過本文提到的HandlerExceptionResolver轉(zhuǎn)化為ModelAndViewObject handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?// 根據(jù)ModelAndView執(zhí)行后續(xù)操作if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}}

    當(dāng)異常發(fā)生時, DispatcherServlet會輪詢調(diào)用HandlerExceptionResolver,直到異常被轉(zhuǎn)化為ModelAndView

    protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}. }

    四、流程總結(jié)

  • 默認(rèn)規(guī)則
    ? 默認(rèn)情況下,Spring Boot提供/error處理所有錯誤的映射
    ? 對于機(jī)器客戶端,它將生成JSON響應(yīng),其中包含錯誤,HTTP狀態(tài)和異常消息的詳細(xì)信息。對于瀏覽器客戶端,響應(yīng)一個“ whitelabel”錯誤視圖,以HTML格式呈現(xiàn)相同的數(shù)據(jù)

  • ? 要對其進(jìn)行自定義,添加View解析為error
    ? 要完全替換默認(rèn)行為,可以實現(xiàn) ErrorController 并注冊該類型的Bean定義,或添加ErrorAttributes類型的組件以使用現(xiàn)有機(jī)制但替換其內(nèi)容。
    ? error/下的4xx,5xx頁面會被自動解析;

  • 定制錯誤處理邏輯
    • 自定義錯誤頁

    error/404.html error/5xx.html;有精確的錯誤狀態(tài)碼頁面就匹配精確,沒有就找4xx.html;如果都沒有就觸發(fā)白頁

    • @ControllerAdvice+@ExceptionHandler處理全局異常;底層是 ExceptionHandlerExceptionResolver 支持的
    • @ResponseStatus+自定義異常 ;底層是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底層調(diào)用 response.sendError(statusCode, resolvedReason);tomcat發(fā)送的/error
    • Spring底層的異常,如 參數(shù)類型轉(zhuǎn)換異常;DefaultHandlerExceptionResolver 處理框架底層的異常。
    • response.sendError(HttpServletResponse.SC_BAD_REQUEST ,ex.getMessage());
      response.sendError會返回tomcat默認(rèn)的錯誤頁(如下),跟springboot的是不一樣的,但springboot底層有個專門的BasicErrorController來處理“"/error" 請求

    • 自定義實現(xiàn) HandlerExceptionResolver 處理異常;可以作為默認(rèn)的全局異常處理規(guī)則

    • ErrorViewResolver 實現(xiàn)自定義處理異常;
    • response.sendError 。error請求就會轉(zhuǎn)給controller
    • 你的異常沒有任何人能處理。tomcat底層response.sendError。error請求就會轉(zhuǎn)給controller
    • basicErrorController要去的頁面地址是 ErrorViewResolver ;
  • 異常處理自動配置原理
    • ErrorMvcAutoConfiguration 自動配置異常處理規(guī)則
      ? 容器中的組件:類型:DefaultErrorAttributes -> id:errorAttributes
    • public class DefaultErrorAttributes implements ErrorAttributes,HandlerExceptionResolver
    • DefaultErrorAttributes:定義錯誤頁面中可以包含哪些數(shù)據(jù)。


    • 容器中的組件:類型:BasicErrorController --> id:basicErrorController(json+白頁 適配響應(yīng))
      • a.處理默認(rèn) /error 路徑的請求;頁面響應(yīng) new ModelAndView(“error”, model);
      • b.容器中有組件 View->id是error;(響應(yīng)默認(rèn)錯誤頁)
      • c.容器中放組件 BeanNameViewResolver(視圖解析器);按照返回的視圖名作為組件的id去容器中找View對象。 ?
    • 容器中的組件:類型:DefaultErrorViewResolver -> id:conventionErrorViewResolver ?
      • a. 如果發(fā)生錯誤,會以HTTP的狀態(tài)碼 作為視圖頁地址(viewName),找到真正的頁面 ? error/404、5xx.html
      • b. 如果想要返回頁面;就會找error視圖【StaticView】。(默認(rèn)是一個白頁) 寫出去json 錯誤頁

  • 異常處理步驟流程
  • 執(zhí)行目標(biāo)方法,目標(biāo)方法運(yùn)行期間有任何異常都會被catch、而且標(biāo)志當(dāng)前請求結(jié)束;并且用 dispatchException
  • 進(jìn)入視圖解析流程(頁面渲染?) processDispatchResult(processedRequest, response,
    mappedHandler, mv, dispatchException);
  • mv =processHandlerException;處理handler發(fā)生的異常,處理完成返回ModelAndView;
    a. 遍歷所有的handlerExceptionResolvers,看誰能處理當(dāng)前異常【HandlerExceptionResolver處理器異常解析器】

    b.系統(tǒng)默認(rèn)的 異常解析器;

    一、DefaultErrorAttributes先來處理異常。把異常信息保存到rrequest域,并且返回null;
    二、默認(rèn)沒有任何人能處理異常,所以異常會被拋出
    • 如果沒有任何人能處理最終底層就會發(fā)送 /error請求。會被底層的BasicErrorController處理
    • 解析錯誤視圖;遍歷所有的 ErrorViewResolver
      看誰能解析。

      三、默認(rèn)的 DefaultErrorViewResolver,作用是把響應(yīng)狀態(tài)碼作為錯誤頁的地址,或者匹配5xx,4xx(序列碼)這種形式找到最終頁面,error/500.html
      四、模板引擎最終響應(yīng)這個頁面 error/500.html
  • 異常處理順序
    攔截時序圖:

    圖片來自網(wǎng)絡(luò)
  • 參考文章
    參考視頻

    總結(jié)

    以上是生活随笔為你收集整理的Spring Boot Spring MVC异常处理原理分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。