web九大组件之---HandlerExceptionResolver异常处理器使用详解【享学Spring MVC】(中)

简介: web九大组件之---HandlerExceptionResolver异常处理器使用详解【享学Spring MVC】(中)

ResponseStatusExceptionResolver


若抛出的异常类型上有@ResponseStatus注解,那么此处理器就会处理,并且状态码会返给response。Spring5.0还能处理ResponseStatusException这个异常(此异常是5.0新增)


// 实现了接口MessageSourceAware,方便拿到国际化资源,方便错误消息的国际化
// @since 3.0
public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
  @Nullable
  private MessageSource messageSource;
  @Override
  public void setMessageSource(MessageSource messageSource) {
    this.messageSource = messageSource;
  }
  @Override
  @Nullable
  protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    try {
      // 若异常类型是,那就处理这个异常
      // 处理很简单:response.sendError(statusCode, resolvedReason)
      // 当然会有国际化消息的处理。最终new一个空的new ModelAndView()供以返回
      if (ex instanceof ResponseStatusException) {
        return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
      }
      // 若异常类型所在的类上标注了ResponseStatus注解,就处理这个状态码
      //(可见:异常类型优先于ResponseStatus)
      // 处理方式同上~~~~
      ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
      if (status != null) {
        return resolveResponseStatus(status, request, response, handler, ex);
      }
      // 这里有个递归:如果异常类型是Course里面的,也会继续处理,所以需要注意这里的递归处理
      if (ex.getCause() instanceof Exception) {
        return doResolveException(request, response, handler, (Exception) ex.getCause());
      }
    } catch (Exception resolveEx) { // 处理失败,就记录warn日志(非info哦~)
      if (logger.isWarnEnabled()) {
        logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx);
      }
    }
    return null;
  }
}


这里有个处理的小细节:递归调用了doResolveException()方法,也就是说若有coouse原因也是异常,那就继续会尝试处理的。

另外请注意:@ResponseStatus标注在异常类上此处理器才会处理,而不是标注在处理方法上,或者所在类上哦,所以一般用于自定义异常时使用。


DefaultHandlerExceptionResolver


默认的异常处理器。它能够处理标准的Spring MVC异常们,并且把它转换为对应的HTTP status codes,一般作为兜底处理,Spring MVC默认也注册了此处理器。它能处理的异常非常之多,简单列出来如下:


image.png


// @since 3.0
public class DefaultHandlerExceptionResolver extends AbstractHandlerExceptionResolver {
  public DefaultHandlerExceptionResolver() {
    setOrder(Ordered.LOWEST_PRECEDENCE);
    setWarnLogCategory(getClass().getName()); // 不同的日志采用不同的记录器是个很好的习惯
  }
  @Override
  @Nullable
  protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    try {
      if (ex instanceof HttpRequestMethodNotSupportedException) {
        return handleHttpRequestMethodNotSupported(
            (HttpRequestMethodNotSupportedException) ex, request, response, handler);
      } else if (ex instanceof HttpMediaTypeNotSupportedException) {
        return handleHttpMediaTypeNotSupported(
            (HttpMediaTypeNotSupportedException) ex, request, response, handler);
      } ... // 省略其它的else if
      // 多有的handle方法几乎一样的,都是response.sendError()
      // 有的还会esponse.setHeader("Accept", MediaType.toString(mediaTypes));等等
  }
}


它对这些异常的处理,亦可参考内置的ResponseEntityExceptionHandler实现,它提供了基于@ExceptionHandler的很多异常类型的处理。


DispatcherServlet对它的初始化和应用


因为Spring MVC对请求的整个处理流程都是由DispatcherServlet来控制的,异常处理也属于请求的一部分,所以它的初始化和应用都在此处。


初始化

虽然异常处理非常重要,但绝大多数情况下你可能并不知道Spring MVC它内置就自动给我们配置好了一些异常处理器。DispatcherServlet初始化它的相关代码如下:


DispatcherServlet:
  protected void initStrategies(ApplicationContext context) {
    ...
    initHandlerExceptionResolvers(context); // 第六步
    ...
  }
  // 寻找逻辑(detectAllHandlerExceptionResolvers默认值是true表示回去容器里寻找):
  // 1、若detect = true(默认是true),去容器里找出所有`HandlerExceptionResolver`类型的Bean们,找到后排序
  // 2、若detect = false(可手动更改),那就拿名称为`handlerExceptionResolver`这单独的一个Bean(context.getBean())
  // 3、如果一个都木有找到,那就走默认策略getDefaultStrategies(),详见下面截图~~~
  private void initHandlerExceptionResolvers(ApplicationContext context) {
    this.handlerExceptionResolvers = null;
    if (this.detectAllHandlerExceptionResolvers) {
      // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
      Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
      if (!matchingBeans.isEmpty()) {
        this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
        // We keep HandlerExceptionResolvers in sorted order.
        AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
      }
    } else {
      try {
        HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
        this.handlerExceptionResolvers = Collections.singletonList(her);
      } catch (NoSuchBeanDefinitionException ex) {
        // Ignore, no HandlerExceptionResolver is fine too.
      }
    }
    // Ensure we have at least some HandlerExceptionResolvers, by registering
    // default HandlerExceptionResolvers if no other resolvers are found.
    if (this.handlerExceptionResolvers == null) {
      this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
      if (logger.isTraceEnabled()) {
        logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() + "': using default strategies from DispatcherServlet.properties");
      }
    }
  }


参照此初始化逻辑,可得出如下结论(缺省情况下):


  • 开启@EnableWebMvc后,使用的异常处理器是HandlerExceptionResolverComposite。截图如下:

image.png

  • 若不开启@EnableWebMvc,就执行默认策略,装配如下处理器

image.png


应用流程


请求交给Handler处理后得到返回结果Result,但result可能会有异常,因此DispatcherServlet会针对性对result做处理:


DispatcherServlet:
  // 处理request请求
  protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    applyDefaultViewName(processedRequest, mv);
    mappedHandler.applyPostHandle(processedRequest, response, mv);
    ... // 全部处理完成后,这中间可以是真正结果,也有可能有异常,交给结果处理器
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    ... // 执行拦截器的AfterCompletion方法
  }
  private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    // 不等于null,说明有异常哦~~~~ 那就处理异常
    if (exception != null) {
      // 此种异常属于Spring MVC内部的异常
      if (exception instanceof ModelAndViewDefiningException) {
        mv = ((ModelAndViewDefiningException) exception).getModelAndView();
      } else {
        // 若是普通异常,就交给方法processHandlerException()去统一处理
        // 从而得到一个异常视图ModelAndView,并且标注errorView = true(若不为null的话)
        Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
      }
    }
    ...
    // 渲染此错误视图(若不为null)
    render(mv, request, response)
    ... 
  }
  protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {
    ...
    ModelAndView exMv = null;
    // 核心处理办法就在此处,exMv 只有有一个视图返回了,就立马停止(短路效果)
    if (this.handlerExceptionResolvers != null) {
      for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
        exMv = resolver.resolveException(request, response, handler, ex);
        if (exMv != null) {
          break;
        }
      }
    } 
    ... // 后面处理viewName等等~~~~~~~
  }


从应用流程上看是比较简单的,但是了解了此处理流程对我们后续使用、定制会有很好的促进作用。





相关文章
|
2月前
|
JSON 前端开发 Java
SSM:SpringMVC
本文介绍了SpringMVC的依赖配置、请求参数处理、注解开发、JSON处理、拦截器、文件上传下载以及相关注意事项。首先,需要在`pom.xml`中添加必要的依赖,包括Servlet、JSTL、Spring Web MVC等。接着,在`web.xml`中配置DispatcherServlet,并设置Spring MVC的相关配置,如组件扫描、默认Servlet处理器等。然后,通过`@RequestMapping`等注解处理请求参数,使用`@ResponseBody`返回JSON数据。此外,还介绍了如何创建和配置拦截器、文件上传下载的功能,并强调了JSP文件的放置位置,避免404错误。
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
60 2
|
3月前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
2月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
154 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
3月前
|
XML 缓存 前端开发
springMVC02,restful风格,请求转发和重定向
文章介绍了RESTful风格的基本概念和特点,并展示了如何使用SpringMVC实现RESTful风格的请求处理。同时,文章还讨论了SpringMVC中的请求转发和重定向的实现方式,并通过具体代码示例进行了说明。
springMVC02,restful风格,请求转发和重定向
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
53 0
|
7月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
204 0
|
7月前
|
开发框架 前端开发 JavaScript
JavaScript云LIS系统源码ASP.NET CORE 3.1 MVC + SQLserver + Redis医院实验室信息系统源码 医院云LIS系统源码
实验室信息系统(Laboratory Information System,缩写LIS)是一类用来处理实验室过程信息的软件,云LIS系统围绕临床,云LIS系统将与云HIS系统建立起高度的业务整合,以体现“以病人为中心”的设计理念,优化就诊流程,方便患者就医。
82 0
|
存储 开发框架 前端开发
[回馈]ASP.NET Core MVC开发实战之商城系统(五)
经过一段时间的准备,新的一期【ASP.NET Core MVC开发实战之商城系统】已经开始,在之前的文章中,讲解了商城系统的整体功能设计,页面布局设计,环境搭建,系统配置,及首页【商品类型,banner条,友情链接,降价促销,新品爆款】,商品列表页面,商品详情等功能的开发,今天继续讲解购物车功能开发,仅供学习分享使用,如有不足之处,还请指正。
170 0
|
开发框架 前端开发 .NET
[回馈]ASP.NET Core MVC开发实战之商城系统(三)
[回馈]ASP.NET Core MVC开发实战之商城系统(三)
107 0