一、Spring MVC为处理异常的前期准备

  1. DispatcherServlet
    入口类,是一个Servlet,是所有请求的分发点
  • 初始化

DispatcherServlet在初始化时会触发onRefresh()方法,此方法会调用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也比较简单,只需要类实现接口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转化为List,保存到属性handlerExceptionResolvers 中this.handlerExceptionResolvers = new ArrayList<HandlerExceptionResolver>(matchingBeans.values());// 对HandlerExceptionResolvers使用据order接口里值进行排序AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}
}
...

到此初始化工作完成

  1. 关于HandlerExceptionResolver(异常解析器)以及常用的实现类
  • HandlerExceptionResolver接口
    HandlerExceptionResolver是一个接口,用于处理网络请求过程中抛出的异常,但是不处理异常本身抛出的异常和视图解析过程中抛出的异常

下图是Spring MVC默认实现的HandlerExceptionResolver类

  • HandlerExceptionResolverComposite
    Spring Boot启动时会默认注册HandlerExceptionResolverComposite对象。此类只是一个组合类,并不进行真正的异常处理。当他捕获异常时他只是将异常轮询委托给注册到它属性里的上的HandlerExceptionResolver类来处理异常,如果处理的结果不为null,则转给下一个处理
@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;
}

默认注册到HandlerExceptionResolverComposite 的属性有以下3个HandlerExceptionResolver,按照优先级排列如下:

  • ExceptionHandlerExceptionResolver
  • ResponseStatusExceptionResolver
  • DefaultHandlerExceptionResolver
  • Spring mvc启动时,初始化所有HandlerExceptionResolver到Spring 容器中在Spring boot在启动时,会初始化WebMvcConfigurationSupport 里配置的Bean, 会创建HandlerExceptionResolverComposite对象,此对象包括3个HandlerExceptionResolver,当他捕获异常时,会使用这3个HandlerExceptionResolver进行处理,详细如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {…@Beanpublic HandlerExceptionResolver handlerExceptionResolver() {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<HandlerExceptionResolver>();configureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {// 添加默认HandlerExceptionResolver类    addDefaultHandlerExceptionResolvers(exceptionResolvers);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;}//  添加默认HandlerExceptionResolverComposite及注册到此对象中的HandlerExceptionResolverCompositeprotected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {// 创建 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);// 创建ResponseStatusExceptionResolver ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);exceptionResolvers.add(responseStatusResolver);// 创建DefaultHandlerExceptionResolverexceptionResolvers.add(new DefaultHandlerExceptionResolver());}protected ExceptionHandlerExceptionResolver createExceptionHandlerExceptionResolver() {return new ExceptionHandlerExceptionResolver();}
}

二、详细介绍这3个HandlerExceptionResolver的作用

  1. ExceptionHandlerExceptionResolver
    使用@ExceptionHandler注解方法处理异常类,使用注解处理异常就有这个类的功劳。默认情况下,这个HandlerExceptionResolver的优先级是最高。
    以下是ExceptionHandlerExceptionResolver运行时属性值
  • 属性exceptionHandlerAdviceCache :存储@Controller里@ExceptionHandler的方法
  • 属性exceptionHandlerAdviceCache:存储@ControllerAdvice里@ExceptionHandler的全局方法

处理异常的关键代码
入口doResolveHandlerMethodException方法会通过 getExceptionHandlerMethod获取对应的@ExceptionHandler方法,如果有找到则执行此方法

  @Overrideprotected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {// 杳找对应的方法@ExceptionHandlerServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}
....if (cause != null) {// 执行异常处理方法exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);}else {// 执行异常处理方法exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);}}
....}
  1. getExceptionHandlerMethod方法:查找特定异常的@ExceptionHandler方法,首先从抛出异常的@Controller类中寻找对应的处理方法,如果没有再从@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,则调用这个方法执行处理,否则返回null
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);if (handlerMethod != null) {// 从抛出异常的@Controller类中自身中寻找对应的处理方法,如果有找到先缓存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) {// 调用方法,返回结果return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);}}// 如果没有再从@ControllerAdvice中查找全局的@ExceptionHandler方法,如果找到,则调用这个方法执行处理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;
}
  1. ResponseStatusExceptionResolver
    使用@ResponseStatus处理异常,将异常转化对应的HTTP的状态码。@ResponseStatus可以定义在Excpetion的子类的类上,也可以定义在被@ExceptionHandler注解的方法上(不过这个需要小心使用,由于ExceptionHandlerExceptionResolver的优先级高,这种方式可能被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;
}

根据ResponseStatus 的值设置返回的http状态码和原因

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)) {// 设置返回的http状态码response.sendError(statusCode);}else {String resolvedReason = (this.messageSource != null ?this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :reason);// 设置返回的http状态码和原因response.sendError(statusCode, resolvedReason);}return new ModelAndView();}
  1. DefaultHandlerExceptionResolver
    默认的HandlerExceptionResolver,将特定异常转化为标准的HTTP的状态码。
    详细如下:左边是异常名称,右边是http的状态码

通过代码解释此类行为, 只列出NoSuchRequestHandlingMethodException相关的转换http错误码的代码,表格里其他异常处理类似

异常处理入口doResolveException方法,如果发现异常是NoSuchRequestHandlingMethodException,则调用方法handleNoSuchRequestHandlingMethod

 // 对于NoSuchRequestHandlingMethodException进行转化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();}
  1. Ordered和实现自定义HandlerExceptionResolver类
    每个具体的HandlerExceptionResolver都会实现Ordered接口,来定义执行的顺序,order值越小,越是优先执行。
    如果要实现自己HandlerExceptionResolver,只需要满足两个条件:
  • 实现接口HandlerExceptionResolver和Ordered
  • 使用类似@Component 的注解注解此类,保证spring启动时创建此类对应的对象即可

三、异常处理流程

  • 当执行@RequestMapping抛出异常,会进入异常处理流程
  • 所有的doPost, doGet等do*的方法都会执行到以下方法:找到真正业务的处理逻辑,并进行处理。

下面的代码是找到本次请求真正要处理的HandlerAdapter 对象,并进行处理,最后调用processDispatchResult对结果进行处理,这是我们关心的内容

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);}
…
}

处理业务执行的结果,处理结束可能是 ModelAndView,也可能是Exception。如果结果是Exception,就需要通过本文提到的HandlerExceptionResolver转化为ModelAndView。然后根据ModelAndView将结果返回给请求方

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转化为ModelAndViewObject handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// Did the handler return a view to render?// 根据ModelAndView执行后续操作if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}…
}

当异常发生时, DispatcherServlet会轮询调用HandlerExceptionResolver,直到异常被转化为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;}}….
}

四、流程总结

  1. 默认规则
    • 默认情况下,Spring Boot提供/error处理所有错误的映射
    • 对于机器客户端,它将生成JSON响应,其中包含错误,HTTP状态和异常消息的详细信息。对于浏览器客户端,响应一个“ whitelabel”错误视图,以HTML格式呈现相同的数据

• 要对其进行自定义,添加View解析为error
• 要完全替换默认行为,可以实现 ErrorController 并注册该类型的Bean定义,或添加ErrorAttributes类型的组件以使用现有机制但替换其内容。
• error/下的4xx,5xx页面会被自动解析;

  1. 定制错误处理逻辑
  • 自定义错误页

error/404.html error/5xx.html;有精确的错误状态码页面就匹配精确,没有就找4xx.html;如果都没有就触发白页

  • @ControllerAdvice+@ExceptionHandler处理全局异常;底层是 ExceptionHandlerExceptionResolver 支持的
  • @ResponseStatus+自定义异常 ;底层是 ResponseStatusExceptionResolver ,把responsestatus注解的信息底层调用 response.sendError(statusCode, resolvedReason);tomcat发送的/error
  • Spring底层的异常,如 参数类型转换异常;DefaultHandlerExceptionResolver 处理框架底层的异常。
  • response.sendError(HttpServletResponse.SC_BAD_REQUEST ,ex.getMessage());
    response.sendError会返回tomcat默认的错误页(如下),跟springboot的是不一样的,但springboot底层有个专门的BasicErrorController来处理“"/error" 请求

  • 自定义实现 HandlerExceptionResolver 处理异常;可以作为默认的全局异常处理规则

  • ErrorViewResolver 实现自定义处理异常;
  • response.sendError 。error请求就会转给controller
  • 你的异常没有任何人能处理。tomcat底层response.sendError。error请求就会转给controller
  • basicErrorController要去的页面地址是 ErrorViewResolver ;
  1. 异常处理自动配置原理
  • ErrorMvcAutoConfiguration 自动配置异常处理规则
    • 容器中的组件:类型:DefaultErrorAttributes -> id:errorAttributes
  • public class DefaultErrorAttributes implements ErrorAttributes,HandlerExceptionResolver
  • DefaultErrorAttributes:定义错误页面中可以包含哪些数据。


  • 容器中的组件:类型:BasicErrorController --> id:basicErrorController(json+白页 适配响应)

    • a.处理默认 /error 路径的请求;页面响应 new ModelAndView(“error”, model);
    • b.容器中有组件 View->id是error;(响应默认错误页)
    • c.容器中放组件 BeanNameViewResolver(视图解析器);按照返回的视图名作为组件的id去容器中找View对象。 •
  • 容器中的组件:类型:DefaultErrorViewResolver -> id:conventionErrorViewResolver •
    • a. 如果发生错误,会以HTTP的状态码 作为视图页地址(viewName),找到真正的页面 • error/404、5xx.html
    • b. 如果想要返回页面;就会找error视图【StaticView】。(默认是一个白页) 写出去json 错误页

  1. 异常处理步骤流程
  1. 执行目标方法,目标方法运行期间有任何异常都会被catch、而且标志当前请求结束;并且用 dispatchException
  2. 进入视图解析流程(页面渲染?) processDispatchResult(processedRequest, response,
    mappedHandler, mv, dispatchException);
  3. mv =processHandlerException;处理handler发生的异常,处理完成返回ModelAndView;
    a. 遍历所有的handlerExceptionResolvers,看谁能处理当前异常【HandlerExceptionResolver处理器异常解析器】

    b.系统默认的 异常解析器;

    一、DefaultErrorAttributes先来处理异常。把异常信息保存到rrequest域,并且返回null;
    二、默认没有任何人能处理异常,所以异常会被抛出
  • 如果没有任何人能处理最终底层就会发送 /error请求。会被底层的BasicErrorController处理
  • 解析错误视图;遍历所有的 ErrorViewResolver
    看谁能解析。

    三、默认的 DefaultErrorViewResolver,作用是把响应状态码作为错误页的地址,或者匹配5xx,4xx(序列码)这种形式找到最终页面,error/500.html
    四、模板引擎最终响应这个页面 error/500.html
  1. 异常处理顺序
    拦截时序图:

    图片来自网络

参考文章
参考视频

Spring Boot Spring MVC异常处理原理分析相关推荐

  1. spring boot + spring mvc 原理解析

    前言:spring mvc 是当前最为流行的一种java WEB 框架.在还没有spring boot以前,通常搭配tomcat等容器进行web项目的开发.而现在spring全家桶越来越完善.慢慢脱离 ...

  2. Spring Boot Spring MVC 异常处理的N种方法 1

    github:https://github.com/chanjarste... 参考文档: Spring Boot 1.5.4.RELEASE Documentation Spring framewo ...

  3. Spring Boot Spring MVC 异常处理的N种方法

    默认行为 根据Spring Boot官方文档的说法: For machine clients it will produce a JSON response with details of the e ...

  4. 在controller中调用指定参数给指定表单_第005课:Spring Boot 中MVC支持

    Spring Boot 的 MVC 支持主要介绍实际项目中最常用的几个注解,包括 @RestController. @RequestMapping.@PathVariable.@RequestPara ...

  5. Spring Boot 前世今生和整体架构分析

    依托于Servlet的Spring Boot spring boot是目前java微服务广泛使用的Web框架,本身内部的核心模块是嵌入的tomcat和spring mvc. spring mvc设计上 ...

  6. Spring Boot入门——全局异常处理

    Spring Boot入门--全局异常处理 参考文章: (1)Spring Boot入门--全局异常处理 (2)https://www.cnblogs.com/studyDetail/p/702758 ...

  7. 基于spring boot的统一异常处理

    基于spring boot的统一异常处理 参考文章: (1)基于spring boot的统一异常处理 (2)https://www.cnblogs.com/knyel/p/7804237.html 备 ...

  8. Spring Boot的自动化配置原理

    转载自 Spring Boot的自动化配置原理 随着Ruby.Groovy等动态语言的流行,相比较之下Java的开发显得格外笨重.繁多的配置.低下的开发效率.复杂的部署流程以及第三方技术集成难度大等问 ...

  9. Spring Boot学习——统一异常处理

    Spring Boot学习--统一异常处理 参考文章: (1)Spring Boot学习--统一异常处理 (2)https://www.cnblogs.com/aston/p/7258834.html ...

最新文章

  1. 对象的序列化和反序列化
  2. 中学计算机基础Word授课ppt,中学信息技术- 计算机硬件与软件基础知识课件.ppt...
  3. 《Linux命令行与shell脚本编程大全 第3版》Shell脚本编程基础---02
  4. python语言必背代码-让你的Python代码实现类型提示功能
  5. 数据库MySQL基础---DDL/DML/DQL
  6. Python读写文件(附完整模块化代码)
  7. [Asp.net 开发系列之SignalR篇]专题五:SignalR支持的平台
  8. interlib系统服务器,Interlib图书馆集群管理系统.docx
  9. SpringSecurity下做POST测试以及传递实体
  10. 微软开源网络攻防模拟工具CyberBattleSim介绍及源码分析
  11. 风险评估在公路安全生命防护工程中的应用
  12. 多列堆积柱形图怎么做_Excel2010中进行制作多列堆积图的操作方法|多列堆积柱状图...
  13. 高等数学上册 第一讲 极限与连续(1)
  14. ie打开本地html页面慢,ie11 第一次浏览jquery+CSS3网页时候延时3秒
  15. Python模拟鼠标按键(长按)
  16. C语言结构体详解(结构体定义,使用,结构体大小等)
  17. 域控内使用策略部署和软件分发
  18. 新冠病毒阴谋论报告:有观点甚至认为是盖茨基金会制造的
  19. 彩色激光同轴位移计在智能手机和平板电脑的应用
  20. 【C语言】文件操作<1>

热门文章

  1. 帝国cms调用相关文章若没有则调取最新文章
  2. Java开发知识之Java中的集合上List接口以及子类讲解.
  3. python - os模块
  4. 什么是 Spring?
  5. 第一次接送宝贝去幼儿园
  6. 【78.89%】【codeforces 746A】Compote
  7. 三层架构与四大天王之——查
  8. PetShop 4.0 详解之四(PetShop之ASP.NET缓存)
  9. Python:条件判断
  10. [云炬创业基础笔记]第二章创业者测试17