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它几乎处于一个被弃用的状态,基本可忽略)。

相关文章
|
1月前
|
Java 开发者 微服务
Spring Boot 入门:简化 Java Web 开发的强大工具
Spring Boot 是一个开源的 Java 基础框架,用于创建独立、生产级别的基于Spring框架的应用程序。它旨在简化Spring应用的初始搭建以及开发过程。
58 6
Spring Boot 入门:简化 Java Web 开发的强大工具
|
2月前
|
负载均衡 算法 Java
除了 Ribbon,Spring Cloud 中还有哪些负载均衡组件?
这些负载均衡组件各有特点,在不同的场景和需求下,可以根据项目的具体情况选择合适的负载均衡组件来实现高效、稳定的服务调用。
114 5
|
2月前
|
XML Java 网络架构
使用 Spring Boot 公开 SOAP Web 服务端点:详细指南
使用 Spring Boot 公开 SOAP Web 服务端点:详细指南
66 0
|
3月前
|
前端开发 JavaScript 开发者
Web组件:一种新的前端开发范式
【10月更文挑战第9天】Web组件:一种新的前端开发范式
89 2
|
3月前
|
前端开发 JavaScript Go
前端开发趋势:从响应式设计到Web组件的探索
【10月更文挑战第1天】前端开发趋势:从响应式设计到Web组件的探索
45 3
|
2月前
|
消息中间件 监控 Kafka
Apache Kafka 成为处理实时数据流的关键组件。Kafka Manager 提供了一个简洁的 Web 界面
随着大数据技术的发展,Apache Kafka 成为处理实时数据流的关键组件。Kafka Manager 提供了一个简洁的 Web 界面,方便管理和监控 Kafka 集群。本文详细介绍了 Kafka Manager 的部署步骤和基本使用方法,包括配置文件的修改、启动命令、API 示例代码等,帮助你快速上手并有效管理 Kafka 集群。
56 0
|
4月前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
66 10
|
4月前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
4月前
|
前端开发 安全 Java
技术进阶:使用Spring MVC构建适应未来的响应式Web应用
【9月更文挑战第2天】随着移动设备的普及,响应式设计至关重要。Spring MVC作为强大的Java Web框架,助力开发者创建适应多屏的应用。本文推荐使用Thymeleaf整合视图,通过简洁的HTML代码提高前端灵活性;采用`@ResponseBody`与`Callable`实现异步处理,优化应用响应速度;运用`@ControllerAdvice`统一异常管理,保持代码整洁;借助Jackson简化JSON处理;利用Spring Security增强安全性;并强调测试的重要性。遵循这些实践,将大幅提升开发效率和应用质量。
75 7
|
4月前
|
前端开发 测试技术 开发者
MVC模式在现代Web开发中有哪些优势和局限性?
MVC模式在现代Web开发中有哪些优势和局限性?