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


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





相关文章
|
1月前
|
开发框架 监控 安全
Windows Defender 导致 Web IIS 服务异常停止排查
某日凌晨IIS服务异常停止,经查为Windows Defender安全补丁KB2267602触发引擎更新,导致系统资源波动,进而引发应用池回收。确认非人为操作,系统无重启。通过分析日志与监控,定位原因为Defender更新后扫描加重负载。解决方案:将IIS及.NET相关路径添加至Defender排除列表,避免业务影响。
295 116
|
1月前
|
缓存 安全 Java
《深入理解Spring》过滤器(Filter)——Web请求的第一道防线
Servlet过滤器是Java Web核心组件,可在请求进入容器时进行预处理与响应后处理,适用于日志、认证、安全、跨域等全局性功能,具有比Spring拦截器更早的执行时机和更广的覆盖范围。
|
2月前
|
存储 安全 Java
如何在 Spring Web 应用程序中使用 @SessionScope 和 @RequestScope
Spring框架中的`@SessionScope`和`@RequestScope`注解用于管理Web应用中的状态。`@SessionScope`绑定HTTP会话生命周期,适用于用户特定数据,如购物车;`@RequestScope`限定于单个请求,适合无状态、线程安全的操作,如日志记录。合理选择作用域能提升应用性能与可维护性。
129 1
|
3月前
|
存储 NoSQL Java
探索Spring Boot的函数式Web应用开发
通过这种方式,开发者能以声明式和函数式的编程习惯,构建高效、易测试、并发友好的Web应用,同时也能以较小的学习曲线迅速上手,因为这些概念与Spring Framework其他部分保持一致性。在设计和编码过程中,保持代码的简洁性和高内聚性,有助于维持项目的可管理性,也便于其他开发者阅读和理解。
134 0
|
4月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
324 0
|
4月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
385 0
|
4月前
|
JSON Java 数据库
第08课:Spring Boot中的全局异常处理
第08课:Spring Boot中的全局异常处理
660 0
|
Java API 数据库
构建RESTful API已经成为现代Web开发的标准做法之一。Spring Boot框架因其简洁的配置、快速的启动特性及丰富的功能集而备受开发者青睐。
【10月更文挑战第11天】本文介绍如何使用Spring Boot构建在线图书管理系统的RESTful API。通过创建Spring Boot项目,定义`Book`实体类、`BookRepository`接口和`BookService`服务类,最后实现`BookController`控制器来处理HTTP请求,展示了从基础环境搭建到API测试的完整过程。
195 4
|
8月前
|
JSON Java 数据格式
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
本文介绍了在Spring Boot项目中如何通过创建`GlobalExceptionHandler`类来全局处理系统异常。通过使用`@ControllerAdvice`注解,可以拦截项目中的各种异常,并结合`@ExceptionHandler`注解针对特定异常(如参数缺失、空指针等)进行定制化处理。文中详细展示了处理参数缺失异常和空指针异常的示例代码,并说明了通过拦截`Exception`父类实现统一异常处理的方法。虽然拦截`Exception`可一劳永逸,但为便于问题排查,建议优先处理常见异常,最后再兜底处理未知异常,确保返回给调用方的信息友好且明确。
1154 0
微服务——SpringBoot使用归纳——Spring Boot中的全局异常处理——处理系统异常
|
8月前
|
移动开发 前端开发 API
鸿蒙web加载本地网页资源异常
在鸿蒙NEXT Api 12中,为解决Web组件加载本地资源(如图片、CSS等)失败的问题,我们采用拦截机制。具体步骤如下: 1. **替换路径**:通过正则表达式将HTML和CSS中的资源路径替换为带有标记的URL(如`http://local`),以便后续识别。 2. **拦截与返回**:在资源加载时,拦截带有标记的URL,读取对应的本地文件并返回给Web组件。此过程确保了本地资源能正确加载和显示。 代码实现包括路径替换、资源拦截及响应构建,确保Web页面能够顺利加载本地资源。
432 7