探索SpringMVC-组件之HandlerExceptionResolver

news2025/2/24 7:17:52

前言

在介绍完Handler、HandlerAdapter、HandlerMapping之后,剩下的比较关键的组件就是HandlerExceptionResolver、ViewResolver。其他的像国际化、主题、文件上传、重定向,这些锦上添花的组件都是一个框架需要关心的。但不是我们平常使用的核心功能,所以有兴趣的同学就自己了解吧。

HandlerExceptionResolver

不管是在处理请求映射(HandlerMapping),还是在请求被处理(Handler)时抛出的异常,DispatcherServlet都会委托给HandlerExceptionResolver进行异常处理。该接口只有一个方法。

	/**
	 * 尝试处理handler执行过程中抛出的异常。可能返回一个代表特定页面的ModelAndView
	 * 如果返回的ModelAndView为空:{ModelAndView#isEmpty()},则表示该异常已经被成功处理,并且不需要渲染视图。例如:通过设置httpStatus处理异常.
	 * @param request 当前HTTP请求
	 * @param response 当前HTTP响应
	 * @param handler 被执行的handler。可能为null,如果异常发生在处理器选择之前(例如:multipart处理失败)
	 * @param ex 处理过程中抛出的异常
	 * @return 对应的ModelAndView。如果无法处理则返回null,DispatcherServlet将使用默认的处理流程。返回new ModelAndView(),则说明请求直接被处理完成了,不需要试图处理。
	 */
	@Nullable
	ModelAndView resolveException(
			HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

SpringMVC提供的异常处理器

HandlerExceptionResolverDescription
SimpleMappingExceptionResolver将异常Class简单的映射到错误视图的名字。常用于浏览器应用渲染错误页面
DefaultHandlerExceptionResolver负责处理SpringMVC抛出的异常,并且将这些异常映射到对应的HTTP状态码。可以对照他的替代者:ResponseEntiryExceptionResolver、Controller Advice
ResponseStatusExceptionResolver通过@ResponseStatus注解来处理异常,并基于注解的值映射到HTTP状态码
ExceptionHandlerExceptionResolver.通过调用@Controller或者@ControllerAdvice中的@ExceptionHandler注解方法来处理异常

接下来,我们介绍一下比较常用的ExceptionHandlerExceptionResolver。

ExceptionHandlerExceptionResolver

如果有同学配置过全局异常处理的,应该会认识这两注解:@ControllerAdvice@ExceptionHandler,而他们正是ExceptionHandlerExceptionResolver处理异常的重要抓手。

全局异常配置

在了解ExceptionHandlerExceptionResolver的设计之前,我们先来看看最常用的全局异常配置是怎样的。只有知道他要达到什么样的目标,才能理解他为什么这么设计/实现!

@ControllerAdvice
public class GlobalExceptionHandler {

	@ExceptionHandler(RuntimeException.class)
	@ResponseBody
	public ResultDTO handleRuntimeException(RuntimeException e, HandlerMethod handlerMethod) {
		// ...
	}
	/**
	 * 这是官方的例子
	 */
	@ExceptionHandler({FileSystemException.class, RemoteException.class})
	public ResponseEntity<String> handle(IOException ex) {
    	// ...
	}
}

好,现在来分析一下需求:

  1. 识别带@ControllerAdvice注解的bean
  2. 识别这些bean中的@ExceptionHandler方法
  3. 根据@ExceptionHandler的条件,找到匹配的异常处理方法
  4. 识别和处理异常处理方法的参数,并准备参数列表
    PS: SpringMVC提供了丰富的可选参数
  5. 识别和处理方法返回值,并通过response给客户端进行响应。

额,有没有觉得似曾相识?对标一下@Controller、@RequestMapping?

Handler领域Exception领域作用/描述
@Controller@ControllerAdvice标记目标处理对象类
@RestController@RestControllerAdvice标记目标处理对象,并表示返回值即为要响应的消息体,通常会被json序列化
@RequestMapping@ExceptionHandler标记目标处理方法,并且包含方法可以处理的匹配条件
方法参数列表灵活多变,需要进行参数解析相较于HandlerAdapter,支持的参数要少一些。例如:不支持@RequestBody参数方法参数
方法返回值灵活多变, 需要进行返回值处理 相较于HandlerAdapter,支持的返回值要少一些。例如不支持ModelAndView,但支持ViewName,不支持异步响应返回值等等方法返回值
是不是高度相似?这也意味着他们在参数解析和返回值处理上高度相似。

异常处理逻辑

与上面的全局异常处理相比,实际上Spring在处理异常时,还需要考虑@Controller中的@ExceptionHandler方法。因此异常的处理分为两部分

处理方法描述
@ControllerAdvice中的@ExceptionHandler这里面的方法是全局性的,所有的@RequestMapping方法只要发生异常且Controller中没有声明异常处理方法,则都会用这些方法处理
@Controller中的@ExceptionHandler这些异常处理方法则只对该Controller有效。即,只能处理该Controller中的@RequestMapping方法异常

由此,我们将不得不有个优先级,同样也是最靠近@RequestMapping优先。只有当对应的@Controller中没有@ExceptionHandler时,才能用全局异常处理方法进行处理。
但是仔细考虑一下,在应用运行过程中,每个类都是固定的,方法也是固定的,方法有什么注解也是固定的。如果在调用时频繁去使用反射遍历所有的方法来获取异常处理方法,是不是不太合理?首先,@Controller类很多时候都没有异常处理方法,做这个遍历操作纯粹是无用功。其次,即使有异常处理方法,每次都遍历所有方法也不合理,应该缓存起来。因此运行时类一般是不会变的。

Spring的设计

因为不管是@ControllerAdvice还是@Controller,解析@ExceptionHandler的方式都是一样的,都要遍历所有方法来寻找。因此可以统一起来。于是抽象出来ExceptionHandlerMethodResolver。先说明,别搞混了哈,我们今天说的ExceptionHandlerExceptionResolver是负责调用ExceptionHandler方法来处理异常的ExceptionResolver[异常处理器],而这个ExceptionHandlerMethodResolver负责解析@ExceptionHandler的MethodResolver[方法解析器],

  • 他负责解析管理类中的@ExceptionHandler方法。mappedMethods可以理解为其异常处理方法的注册中心。
  • 由于异常可以嵌套,为了加速匹配,还搞了一个缓存exceptionLookupCache。该缓存使用的是Spring的ConcurrentReferenceHashMap,整个Entry都是软引用,即发生OOM异常之前,key、value都会被清理。(key是异常类型,value是异常处理方法)

ExceptionHandlerExceptionResolver则需要统筹之前说的@ControllerAdvice和@Controller的异常处理。

  • exceptionHandlerCache
    key是handlerType(HandlerMethod对应的@Controller对象类型),value是与之对应的ExceptionHandlerMethodResolver
  • exceptionHandlerAdviceCache
    缓存@ControllerAdvice对象的ExceptionHandlerMethodResolver
    key是ControllerAdviceBean(他实际上封装了@ControllerAdvice的类型信息,同时也可以通过beanFactory拿到相应的bean),value是与该对象对应的ExceptionHandlerMethodResolver

核心处理逻辑

核心处理逻辑在ExceptionHandlerExceptionResolver#doResolveHandlerMethodException


	/**
	 * 寻找一个@ExceptionHandler方法并调用他处理抛出的异常
	 */
	@Override
	@Nullable
	protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
			HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
		// 1. 获取匹配的异常处理方法,并封装成ServletInvocableHandlerMethod
		// 就是这个方法控制着优先使用@Controller中的@ExceptionHandler方法
		// 他会首先检查exceptionHandlerCache,然后才到exceptionHandlerAdviceCache。他们都是通过ExceptionHandler**Method**Resolver找到目标方法的。
		ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
		// 没有异常处理方法,则直接退出
		if (exceptionHandlerMethod == null) {
			return null;
		}
		// 初始化exceptionHandlerMethod。主要是设置参数解析器、返回值处理器
		// 省略...

		// 2. 准备exceptionHandlerMethod的调用参数
		ServletWebRequest webRequest = new ServletWebRequest(request, response);
		ModelAndViewContainer mavContainer = new ModelAndViewContainer();

		ArrayList<Throwable> exceptions = new ArrayList<>();
		try {
			// 遍历嵌套异常作为方法参数
			Throwable exToExpose = exception;
			while (exToExpose != null) {
				exceptions.add(exToExpose);
				Throwable cause = exToExpose.getCause();
				exToExpose = (cause != exToExpose ? cause : null);
			}
			Object[] arguments = new Object[exceptions.size() + 1];
			exceptions.toArray(arguments);  // efficient arraycopy call in ArrayList
			arguments[arguments.length - 1] = handlerMethod;
			
			// 调用exceptionHandlerMethod处理异常
			// 该方法的调用就跟RequestMappingHandlerAdapter是一样的了
			exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);
		}
		catch (Throwable invocationEx) {
			// 继续默认的异常处理
			return null;
		}

		if (mavContainer.isRequestHandled()) {
			// 表示异常已被处理完成
			return new ModelAndView();
		}
		else {
			// 从ModelAndViewContainer封装ModelAndView返回
			// 省略...
			return mav;
		}
	}

全貌-UML类图

最后给大家补一张全景图:ExceptionHandlerExceptionResolver的UML类图
ExceptionHandlerExceptionResolver

总结

  1. 异常处理会优先使用对应的@Controller中的@ExceptionHandler方法,然后才是@ControllerAdvice中的异常处理方法。

  2. 从宏观层面,@ExceptionHandler的缓存分为两层

    层次缓存所在注册中心或缓存描述
    类层面ExceptionHandlerExceptionResolver管理@ControllerAdvice的exceptionHandlerAdviceCache以及管理@Controller的exceptionHandlerCache每个BeanType对应一个ExceptionHandlerMethodResolver
    方法层ExceptionHandlerMethodResolver管理@ExceptionHandler方法的mappedMethods。负责加速寻找处理方法的exceptionLookupCache每个类都可能有多个@ExceptionHandler
  3. 方法调用与RequestMappingHandlerAdapter一样,都是通过ServletInvocableHandlerMethod进行处理。

后记

如果理解了RequestMappingHandlerAdapter那么再来理解这个ExceptionHandlerExceptionResolver应该相对简单些,只需要重点理解两个点:

  1. @ExceptionHandler的出现的位置:@ControllerAdvice和@Controller。
  2. @ExceptionHandler的分层设计。

好了,下回咱们该聊聊ViewResovler了。

上一篇:
探索SpringMVC-HandlerMapping之RequestMappingHandlerMapping
第一篇:
探索SpringMVC-web上下文


202301112008-GP

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/158890.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

蓝桥杯省赛习题练习(三)

题目来源&#xff1a;2022年第十三届省赛(B组)真题 目录1.九进制转十进制运行结果2. 顺子日期运行结果3. 刷题统计运行结果4. 积木画1.九进制转十进制 问题描述&#xff1a;九进制正整数 (2022)9 转换成十进制等于多少&#xff1f; #include<stdio.h> #include<math.h…

一文带你深入了解线程池

目录一. 什么是线程池二. 为什么要使用线程池三. 线程池的参数四. 线程池的工作流程五. 使用Executors 创建常见的功能线程池一. 什么是线程池 简单来说&#xff0c;线程池就是提前创建好一批线程&#xff0c;当有任务的时候&#xff0c;从池子中取出一个线程去执行该任务&…

再说多线程(二)——细说Monitor类

在上一节我们已经讨论了使用Lock来保证并发程序的一致性&#xff0c;Lock锁是借助了Monitor类的功能。本节将详细的介绍Monitor类&#xff0c;以及如何通过Monitor类的成员函数实现并行程序的一致性。1.Monitor类介绍根据微软的说法&#xff0c;C#中的监视器类提供了一种同步对…

Microsoft Visual SourceSafe的使用

1、介绍 Microsoft Visual SourceSafe&#xff0c;简称vss。是一款早期微软推出的版本管理工具。跟据官方的定义&#xff0c;vss有两种控制模式&#xff1a;独占&#xff08;Lock-Modify-Unlock Model&#xff09;和并行&#xff08;Copy-Modify-Merge Model&#xff09;。独占…

程序的安装——软件安装包的制作、软件源的使用

读书笔记 —— 《嵌入式C语言自我修养》 软件安装 linux 安装包的制作 编译 软件安装包路径 使用dpkg命令来制作安装包 及 安装包的卸载 软件仓库 更新源 查看具体需要更新的软件包 更新软件包 软件安装 软件安装的过程其实就是将一个可执行文件安装到ROM的过…

安全狗云原生安全从1.X到2.X的演变之路(1)

随着云计算技术的蓬勃发展&#xff0c;传统上云实践中的应用升级缓慢、架构臃肿、无法快速迭代等“痛点”日益明显。能够有效解决这些“痛点”的云原生技术正蓬勃发展&#xff0c;成为赋能业务创新的重要推动力&#xff0c;并已经应用到企业核心业务。然而&#xff0c;云原生技…

大型数据中心分层分布式谐波治理方案设计与效果分析

摘要&#xff1a;数据中心行业在国民经济中起到了不可替代的作用,但其繁多的非线性电力负载,如通讯系统、大型计算机、网络控制设备、变频空调、各种数码办公设备、灯光调控系统、UPS、监控系统等给其供电系统带来了严重的谐波干扰,对大型数据中心的运行安全造成了较大的威胁,为…

200:vue+openlayers 添加删除多边形,modify feature,双向互动颜色显示

第200个 点击查看专栏目录 本示例的目的是介绍如何在vue+openlayers项目中添加删除多边形,每绘制一个,左侧列表出一个信息。 hover左侧文字,右边地图上红色显示图形,点击选中右侧地图上某feature,变成蓝色高亮,同时左侧也会蓝色显示,做到双向互动。 高亮显示某feature,…

如何删除图片数据中的重复数据

我们在工作中经常这种情况&#xff0c;leader给你一堆数据&#xff0c;让你用这些没有清洗过的数据完成项目。痛苦的是&#xff0c;这批数据居然存在很多重复的样本。那如何删除这些冗余数据呢&#xff1f;imagehash库非常好用。 github地址&#xff1a;https://github.com/ch…

络达开发----如何开启DMIC

芯片型号&#xff1a;AB1565 功能模块&#xff1a;数字MIC接口的使用 AB1656评估板上支持两路数字MIC&#xff0c;分别为DMIC0和DMIC1&#xff0c;如果图1所示&#xff0c;分别 可以由GPIO_2/3/4/5/13/14/15/16来当数字MIC的接口。 图1&#xff1a;支持DMIC的IO口但是评估板上…

MAC M1使用Rosetta安装python3.6

在使用网上提到的brew和pyenv安装的时候&#xff0c;我的电脑总会报BUILD FAILED错误。 找了一天才找到解决办法&#xff0c;真的十分痛苦&#xff0c;特此记录一下&#xff0c;让别的小伙伴也不再迷茫。 解决办法参考网址&#xff1a;click here&#xff08;需要VPN&#xff…

html跑马灯走马灯效果

演示 <marquee width"100%" scrollamount"5"> <a href"http://www.taobaojp5.tk"><font face"楷体_GB2312" color"#ff0000" size"3"></font><strong>带有超链接的跑马灯!点我试试&…

uwsgi 快速入门

文章目录uwsgi 快速入门一、 概述1、 简单介绍2、 环境配置二、 第一个 WSGI 应用1、 运行2、 添加并发三、 结合 Web 服务器使用1、 Flask2、 Django3、 Nginx配置uwsgi 快速入门 一、 概述 1、 简单介绍 WSGI&#xff08;Web Server Gateway Interface&#xff09;&#x…

FPGA知识汇集-ASIC移植中的FPGA芯片划分

通常&#xff0c;FPGA单芯片难以容纳下整个ASIC设计&#xff0c;因此需要将整个系统划分到多颗FPGA芯片中运行&#xff08;见图1&#xff09;&#xff0c;工程师往往需要借助原型验证平台来实现这样的目标。多芯片的划分绝不是简单的将不同的模板放置到不同的FPGA中那么简单&am…

算法训练营 day16 二叉树 二叉树的最大深度 二叉树的最小深度 完全二叉树的节点个数

算法训练营 day16 二叉树 二叉树的最大深度 二叉树的最小深度 完全二叉树的节点个数 二叉树的最大深度 104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 给定一个二叉树&#xff0c;找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点…

(JVM) 沙箱安全机制

沙箱安全机制 沙箱安全机制 保证程序安全 保护Java原生的JDK代码 Java安全模型的核心就是Java沙箱&#xff08;sandbox&#xff09;。什么是沙箱&#xff1f;沙箱是一个限制程序运行的环境。 沙箱机制就是将Java代码限定在虚拟机&#xff08;JVM&#xff09;特定的运行范围…

Javaweb+Vue开发中常见的问题-1

1.Vue乱码问题 Vue2 修改打包文件的编码格式&#xff08;webpack-encoding-plugin&#xff09;_一碗单炒饭的博客-CSDN博客_vue如何设置打包文件的编码格式 怎么把命令行改成utf_8 - 沿途百知 设置命令行的编码格式&#xff1a;chcp 65001 https://bbs.csdn.net/topics/3935426…

基于微信小程序云开发的职业学校招生报名小程序源码,职业学校招生报名微信小程序源码 ,职业学校招生报名小程序源码

功能介绍 这是一个以报名为核心的职业学校招生小程序&#xff0c;目的是方便想要系统学习技能&#xff0c;入门某项技能或者领域的初高中毕业生&#xff0c;了解该学校的基本情况及各个专业&#xff0c;并提供报名路径&#xff0c;致力于技能型人才培养。本程序前后端代码完整…

ikun运球新姿势-- 反弹shell

目录 反弹Shell 反弹shell的概述 正向连接 反向连接 为什么需要反弹shell 利用netcat反弹shell 利用Bash反弹shell curl配合Bash反弹shell 将反弹shell的命令写入定时任务 将反弹shell的命令写入/etc/profile文件 python脚本反弹shell 反弹Shell 反弹shell的概述 …

免费的移动硬盘数据恢复软件EasyRcovery15

在日常工作中&#xff0c;移动硬盘可以帮助用户存储重要的文件资料&#xff0c;作为可移动的存储设备&#xff0c;在外出工作时携带起来也比较的方便&#xff0c;而且它的存储空间大&#xff0c;不会出现数据文件过大而无法储存的情况。今天小编就来和大家分享一下&#xff0c;…