javascript
Spring Boot Spring MVC异常处理原理分析
一、Spring MVC為處理異常的前期準(zhǔn)備
入口類,是一個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);} } ...到此初始化工作完成
- 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)給下一個處理
默認(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ì)如下:
二、詳細(xì)介紹這3個HandlerExceptionResolver的作用
使用@ExceptionHandler注解方法處理異常類,使用注解處理異常就有這個類的功勞。默認(rèn)情況下,這個HandlerExceptionResolver的優(yōu)先級是最高。
以下是ExceptionHandlerExceptionResolver運(yùn)行時屬性值
- 屬性exceptionHandlerAdviceCache :存儲@Controller里@ExceptionHandler的方法
- 屬性exceptionHandlerAdviceCache:存儲@ControllerAdvice里@ExceptionHandler的全局方法
處理異常的關(guān)鍵代碼
入口doResolveHandlerMethodException方法會通過 getExceptionHandlerMethod獲取對應(yīng)的@ExceptionHandler方法,如果有找到則執(zhí)行此方法
使用@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();}默認(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();}每個具體的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)情況下,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 錯誤頁
mappedHandler, mv, dispatchException);
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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 投递简历得不到回复,并不是你的简历不好,
- 下一篇: SpringBoot Web原生组件注入