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等等~~~~~~~
  }


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





相关文章
|
9天前
|
设计模式 前端开发 Java
步步深入SpringMvc DispatcherServlet源码掌握springmvc全流程原理
通过对 `DispatcherServlet`源码的深入剖析,我们了解了SpringMVC请求处理的全流程。`DispatcherServlet`作为前端控制器,负责请求的接收和分发,处理器映射和适配负责将请求分派到具体的处理器方法,视图解析器负责生成和渲染视图。理解这些核心组件及其交互原理,有助于开发者更好地使用和扩展SpringMVC框架。
24 4
|
27天前
|
开发框架 Java UED
如何使用 Spring Boot 实现异常处理
如何使用 Spring Boot 实现异常处理
39 2
|
27天前
|
前端开发 Java 开发者
Spring MVC中的请求映射:@RequestMapping注解深度解析
在Spring MVC框架中,`@RequestMapping`注解是实现请求映射的关键,它将HTTP请求映射到相应的处理器方法上。本文将深入探讨`@RequestMapping`注解的工作原理、使用方法以及最佳实践,为开发者提供一份详尽的技术干货。
102 2
|
1月前
|
Dubbo Java 应用服务中间件
深入探讨了“dubbo+nacos+springboot3的native打包成功后运行出现异常”的原因及解决方案
本文深入探讨了“dubbo+nacos+springboot3的native打包成功后运行出现异常”的原因及解决方案。通过检查GraalVM版本兼容性、配置反射列表、使用代理类、检查配置文件、禁用不支持的功能、查看日志文件、使用GraalVM诊断工具和调整GraalVM配置等步骤,帮助开发者快速定位并解决问题,确保服务的正常运行。
52 1
|
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 API Spring
springBoot:注解&封装类&异常类&登录实现类 (八)
本文介绍了Spring Boot项目中的一些关键代码片段,包括使用`@PathVariable`绑定路径参数、创建封装类Result和异常处理类GlobalException、定义常量接口Constants、自定义异常ServiceException以及实现用户登录功能。通过这些代码,展示了如何构建RESTful API,处理请求参数,统一返回结果格式,以及全局异常处理等核心功能。
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
65 2
|
2月前
|
XML 前端开发 Java
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
本文阐述了Spring、Spring Boot和Spring MVC的关系与区别,指出Spring是一个轻量级、一站式、模块化的应用程序开发框架,Spring MVC是Spring的一个子框架,专注于Web应用和网络接口开发,而Spring Boot则是对Spring的封装,用于简化Spring应用的开发。
192 0
Spring,SpringBoot和SpringMVC的关系以及区别 —— 超准确,可当面试题!!!也可供零基础学习
|
4月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
60 0
|
7月前
|
开发框架 前端开发 .NET
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
ASP.NET CORE 3.1 MVC“指定的网络名不再可用\企图在不存在的网络连接上进行操作”的问题解决过程
214 0