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

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

前言


任何程序都会有异常。无论你是做什么项目,对异常的处理都是非常有必要的,尤其是web项目,因为它一般直接面向用户,所以良好的异常处理就显得格外的重要。Spring MVC作为如此优秀的web层框架,自然考虑到了这一点,因此它从首个版本便提供了异常处理器HandlerExceptionResolver,这便是本文的主要议题。

Java异常体系简介



Java相较于其它大多数语言提供了一套非常完善的异常体系Throwable:分为Error和Exception两大分支:


  1. Error:错误,对于所有的编译时期的错误以及系统错误都是通过Error抛出的,比如NoClassDefFoundError、Virtual MachineError、ZipError、硬件问题等等。
  2. Exception:异常,是更为重要的一个分支,是程序员经常打交道的。异常定义为是程序的问题,程序本身是可以处理的。


Error和Exception最大的区别是:异常是可以被程序处理的,而错误是没法处理的。


错误是不可查的,因为它们在应用程序的控制和处理能力之外,而且绝大多数是程序运行时不允许出现的状况(比如类找不到NoClassDefFoundError)


当然喽,异常Exception它本身还分为两大重要的分支:Checked Exception(可检查异常,如IOException)和Unchecked Exception(不可检查异常,如RuntimeException)。这部分不是本文关注的重点,此处只稍微提一下而已。


tips:RuntimeException不仅可以throw,也是可以throws的。只是若它throws的话,它人调用此方法时并不需要强制catch/继续throws(和IOException不同),所以我们一般不这么来用,但是语法上是允许的哦~


为何需要全局异常处理?


在web项目开发时,我们一般把业务代码(大量代码)写在Service层。作为面向返回的Controller层就需要关注一些异常情况了:如此一来,我们的Controller层就不得不进行try-catch,形如这样子:


@GetMapping("/test")
public String test() {
  try {
    ... // 处理你的业务逻辑
    return "success";
  } catch (Exception e) {
    return "fail"; // 处理异常
  }
}


显然,这么处理至少有如下两大问题:


  1. Controller一般方法众多,那就需要写大量的try-catch代码,很难看也很难维护
  2. 在此处try-catch也只能捕获住Handler的异常,万一是view抛出异常了呢???


一句话:如果你能够保证你的程序不会出错(没有bug),那么你是不需要全局异常处理的,因为压根就不会发生异常嘛(nnp都不会哦~),很显然这太过于不现实了。

还有一个重要原因:即使你的程序出现了异常(因为避免不了),你总不能把一些只有程序员才能看懂的错误代码抛给用户去看吧,因此展现一个比较友好的错误页面就显得很有必要了,这就是全局异常处理。


我记得滴滴在创业早期出了这么一个"事故":那时滴滴、快的竞争白热化,滴滴司机在APP上提现时竟然弹出:“余额不足”的提示(虽然是真的滴滴账户余额不足了,但你也不能给出这种提示呀),这个提示差点葬送了滴滴的大好前程。从大了来讲,这其实也属于异常处理的范畴咯。


既然异常处理这么重要,那么本文就重点讨论Spring MVC它提供的对异常处理的支持。


古老的异常处理方式


在还没有Spring,更无Spring Boot时,开发使用的是源生的Servlet + tomcat容器。其实它也是提供了通用的异常的处理配置方式的(自己控制response的方式不在本文讨论访问内)。如果你是“老”程序员,你应该在web.xml里看到过如下配置:


<!-- 根据状态码 -->
<error-page>
    <error-code>500</error-code>
    <location>/500.jsp</location>
</error-page>
<!-- 根据异常类型 -->
<error-page>
  <exception-type>java.lang.RuntimeException</exception-type>
  <location>/500.jsp</location>
</error-page>


配置上的效果很容易理解,这里就不赘述。但是显然这种做法已经完全落伍了,毕竟web.xml都已经被淘汰了嘛,所以我此处把它称为古老的异常处理方式。


Spring MVC处理异常


Spring MVC作为现在the most known的Web框架产品,优雅异常处理这块它当然提供了完善的支持。Spring MVC提供处理异常的方式主要分为两种:


  1. 实现HandlerExceptionResolver方式
  2. @ExceptionHandler注解方式。注解方式也有两种用法:1. 使用在Controller内部2. 配置@ControllerAdvice一起使用实现全局处理


本文作为入门篇,将先聚焦于第一种方式的使用和分析。


HandlerExceptionResolver


它是Spring首个版本就提供了的异常处理器接口,定义也非常的简单:


// @since 22.11.2003
public interface HandlerExceptionResolver {
  // 注意:handler是有可能为null的,比如404
  @Nullable
  ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}


处理方法返回一个ModelAndView视图:既可以是json,也可以是页面。从接口参数上可以发现的是:它只能处理Exception,因为Error是程序处理不了的(注意:Error也是可以捕获的),因此入参类型若写成Throwable是不合适的。


可能有人会问为何不捕获Error呢?此处简答一下:因为出现Error的情况会造成程序直接无法运行,所以捕获了也没有任何意义。


它的继承树如下:


image.png

HandlerExceptionResolverComposite这种模式的类已经非常熟悉了,就不用再分析了,它实现的是短路效果:只要有一个Resolver返回了不为null的视图就截止了,否则继续处理。多个处理器的顺序可用Ordered控制(需要注意的是:若你是HandlerExceptionResolverComposite#add进来的,那order是不生效的请手动控制此ArrayList)~


AbstractHandlerExceptionResolver


可以看到所有其它子类的实现都是此抽象类的子类,所以若我们自定义异常处理器,我也推荐从此处去继承,它是Spring3.0后才有的。它主要是提供了对异常更细粒度的控制:此Resolver可只处理指定类型的异常。


// @since 3.0
public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {
  ...
  private int order = Ordered.LOWEST_PRECEDENCE;
  // 可以设置任何的handler,表示只作用于这些Handler们
  @Nullable
  private Set<?> mappedHandlers;
  // 表示只作用域这些Class类型的Handler们~~~
  @Nullable
  private Class<?>[] mappedHandlerClasses;
  // 以上两者若都为null,那就是匹配素有。但凡有一个有值,那就需要精确匹配(并集的关系)
  ... // 省略所有的get/set方法
  @Override
  @Nullable
  public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    // 这个作用匹配逻辑很简答
    // 若mappedHandlers和mappedHandlerClasses都为null永远返回true
    // 但凡配置了一个就需要精确匹配(并集关系)
    // 需要注意的是:shouldApplyTo方法,子类AbstractHandlerMethodExceptionResolver是有复写的
    if (shouldApplyTo(request, handler)) {
      // 是否执行;response.addHeader(HEADER_CACHE_CONTROL, "no-store")  默认是不执行的
      prepareResponse(ex, response);
      // 此抽象方法留给子类去完成~~~~~
      ModelAndView result = doResolveException(request, response, handler, ex);
      return result;
    } else { // 若此处理器不处理,就返回null呗
      return null;
    }
  }
}


此抽象类主要是提供setMappedHandlers和setMappedHandlerClasses让此处理器可以作用在指定类型/处理器上,因此子类只要继承了它都将会有这种能力,这也是为何我推荐自定义实现也继承于它的原因。它提供了shouldApplyTo()方法用于匹配逻辑,子类若想定制化匹配规则,亦可复写此方法。

SimpleMappingExceptionResolver


顾名思义它就是通过简单映射关系来决定由哪个错误视图来处理当前的异常信息。它提供了多种映射关系可以使用:


  1. 通过异常类型Properties exceptionMappings;映射。它的key可以是全类名、短名称,同时还有继承效果:比如key是Exception那将匹配所有的异常。value是view name视图名称1. 若有需要,可以配合Class<?>[] excludedExceptions来一起使用
  2. 通过状态码Map<String, Integer> statusCodes匹配。key是view name,value是http状态码


它的源码部分,我们只需要关心下面这一个方法就可以了:


SimpleMappingExceptionResolver:
  @Override
  @Nullable
  protected ModelAndView doResolveException(
      HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {
    // 根据异常类型去exceptionMappings匹配到一个viewName
    // 实在木有匹配到,就用的defaultErrorView(当然defaultErrorView也可能为null没配置,不过建议配置)
    String viewName = determineViewName(ex, request);
    if (viewName != null) {
      // 如果匹配上了一个视图后,再去使用视图匹配出一个statusCode
      // 若没匹配上就用defaultStatusCode(当然它也有可能为null)
      Integer statusCode = determineStatusCode(request, viewName);
      if (statusCode != null) {
        //  执行response.setStatus(statusCode)
        applyStatusCodeIfPossible(request, response, statusCode);
      }
      // new ModelAndView(viewName) 设置好viewName
      // 并且,并且,并且:mv.addObject(this.exceptionAttribute, ex)把异常信息放进去。exceptionAttribute的值默认为:exception
      return getModelAndView(viewName, ex, request);
    } else {
      return null;
    }
  }


此类是Spring首个版本就内置的,其它的均是Spring3.0+才出现。此简单映射功能还算强大,但使用起来有诸多不便,因此Spring MVC默认情况下并没有装配上它(so它几乎处于一个被弃用的状态,基本可忽略)。

相关文章
|
4月前
|
前端开发 Java 微服务
《深入理解Spring》:Spring、Spring MVC与Spring Boot的深度解析
Spring Framework是Java生态的基石,提供IoC、AOP等核心功能;Spring MVC基于其构建,实现Web层MVC架构;Spring Boot则通过自动配置和内嵌服务器,极大简化了开发与部署。三者层层演进,Spring Boot并非替代,而是对前者的高效封装与增强,适用于微服务与快速开发,而深入理解Spring Framework有助于更好驾驭整体技术栈。
|
10月前
|
NoSQL 安全 Java
深入理解 RedisConnectionFactory:Spring Data Redis 的核心组件
在 Spring Data Redis 中,`RedisConnectionFactory` 是核心组件,负责创建和管理与 Redis 的连接。它支持单机、集群及哨兵等多种模式,为上层组件(如 `RedisTemplate`)提供连接抽象。Spring 提供了 Lettuce 和 Jedis 两种主要实现,其中 Lettuce 因其线程安全和高性能特性被广泛推荐。通过手动配置或 Spring Boot 自动化配置,开发者可轻松集成 Redis,提升应用性能与扩展性。本文深入解析其作用、实现方式及常见问题解决方法,助你高效使用 Redis。
1071 4
|
7月前
|
前端开发 Java API
Spring Cloud Gateway Server Web MVC报错“Unsupported transfer encoding: chunked”解决
本文解析了Spring Cloud Gateway中出现“Unsupported transfer encoding: chunked”错误的原因,指出该问题源于Feign依赖的HTTP客户端与服务端的`chunked`传输编码不兼容,并提供了具体的解决方案。通过规范Feign客户端接口的返回类型,可有效避免该异常,提升系统兼容性与稳定性。
524 0
|
7月前
|
JSON 前端开发 Java
Spring MVC 核心组件与请求处理机制详解
本文解析了 Spring MVC 的核心组件及请求流程,核心组件包括 DispatcherServlet(中央调度)、HandlerMapping(URL 匹配处理器)、HandlerAdapter(执行处理器)、Handler(业务方法)、ViewResolver(视图解析),其中仅 Handler 需开发者实现。 详细描述了请求执行的 7 步流程:请求到达 DispatcherServlet 后,经映射器、适配器找到并执行处理器,再通过视图解析器渲染视图(前后端分离下视图解析可省略)。 介绍了拦截器的使用(实现 HandlerInterceptor 接口 + 配置类)及与过滤器的区别
677 0
|
7月前
|
SQL Java 数据库连接
Spring、SpringMVC 与 MyBatis 核心知识点解析
我梳理的这些内容,涵盖了 Spring、SpringMVC 和 MyBatis 的核心知识点。 在 Spring 中,我了解到 IOC 是控制反转,把对象控制权交容器;DI 是依赖注入,有三种实现方式。Bean 有五种作用域,单例 bean 的线程安全问题及自动装配方式也清晰了。事务基于数据库和 AOP,有失效场景和七种传播行为。AOP 是面向切面编程,动态代理有 JDK 和 CGLIB 两种。 SpringMVC 的 11 步执行流程我烂熟于心,还有那些常用注解的用法。 MyBatis 里,#{} 和 ${} 的区别很关键,获取主键、处理字段与属性名不匹配的方法也掌握了。多表查询、动态
221 0
|
7月前
|
JSON 前端开发 Java
第05课:Spring Boot中的MVC支持
第05课:Spring Boot中的MVC支持
327 0
|
4月前
|
算法 Java Go
【GoGin】(1)上手Go Gin 基于Go语言开发的Web框架,本文介绍了各种路由的配置信息;包含各场景下请求参数的基本传入接收
gin 框架中采用的路优酷是基于httprouter做的是一个高性能的 HTTP 请求路由器,适用于 Go 语言。它的设计目标是提供高效的路由匹配和低内存占用,特别适合需要高性能和简单路由的应用场景。
439 4
|
8月前
|
缓存 JavaScript 前端开发
鸿蒙5开发宝藏案例分享---Web开发优化案例分享
本文深入解读鸿蒙官方文档中的 `ArkWeb` 性能优化技巧,从预启动进程到预渲染,涵盖预下载、预连接、预取POST等八大优化策略。通过代码示例详解如何提升Web页面加载速度,助你打造流畅的HarmonyOS应用体验。内容实用,按需选用,让H5页面快到飞起!
|
8月前
|
JavaScript 前端开发 API
鸿蒙5开发宝藏案例分享---Web加载时延优化解析
本文深入解析了鸿蒙开发中Web加载完成时延的优化技巧,结合官方案例与实际代码,助你提升性能。核心内容包括:使用DevEco Profiler和DevTools定位瓶颈、四大优化方向(资源合并、接口预取、图片懒加载、任务拆解)及高频手段总结。同时提供性能优化黄金准则,如首屏资源控制在300KB内、关键接口响应≤200ms等,帮助开发者实现丝般流畅体验。
|
前端开发 JavaScript Shell
鸿蒙5开发宝藏案例分享---Web页面内点击响应时延分析
本文为鸿蒙开发者整理了Web性能优化的实战案例解析,结合官方文档深度扩展。内容涵盖点击响应时延核心指标(≤100ms)、性能分析工具链(如DevTools时间线、ArkUI Trace抓取)以及高频优化场景,包括递归函数优化、网络请求阻塞解决方案和setTimeout滥用问题等。同时提供进阶技巧,如首帧加速、透明动画陷阱规避及Web组件初始化加速,并通过优化前后Trace对比展示成果。最后总结了快速定位问题的方法与开发建议,助力开发者提升Web应用性能。