【Spring专题】「技术原理」从源码角度去深入分析关于Spring的异常处理ExceptionHandler的实现原理

news2024/11/9 6:17:58

ExceptionHandler的作用

ExceptionHandler是Spring框架提供的一个注解,用于处理应用程序中的异常。当应用程序中发生异常时,ExceptionHandler将优先地拦截异常并处理它,然后将处理结果返回到前端。该注解可用于类级别和方法级别,以捕获不同级别的异常。

在Spring中使用ExceptionHandler非常简单,只需在需要捕获异常的方法上注解@ExceptionHandler,然后定义一个方法,该方法将接收异常并返回异常信息,并将该异常信息展示给前端用户。

ExceptionHandler的使用

说明:针对可能出问题的Controller,新增注解方法@ExceptionHandler,下面是一个基本的ExceptionHandler示例

@RestController
public class ExceptionController {
	
    @ExceptionHandler(Exception.class)
    public ResponseEntity<String> handleException(Exception ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("An error occurred: " + ex.getMessage());
    }
    @RequestMapping("/test")
    public String test() throws Exception {
        throw new Exception("Test exception!");
    }
}

在上面的示例中,我们定义了一个叫做ExceptionController的类,该类是一个**@RestController**注解的控制器,它包括一个可以产生异常的请求处理程序,一个用于捕获和处理异常的@ExceptionHandler方法。

@RequestMapping注解配置了一个名为“/test”的API,该API将抛出一个异常,该异常将由我们上面的ExceptionHandler进行处理。当请求“/test”时,Controller方法将引发异常并触发@ExceptionHandler方法。

在上面的@ExceptionHandler方法中,我们通过ResponseEntity将异常信息提供给客户端,HTTP状态码设置为500。这使客户端了解已发生错误,并能够在日志中记录异常信息以便日后调试。

总之,使用ExceptionHandler能够更好的掌控应用的异常信息,使得应用在发生异常的时候更加可控,并且更加容易进行调试

ExceptionHandler的注意事项

  • Controller类下多个**@ExceptionHandler**上的异常类型不能出现一样的,否则运行时抛异常。

  • @ExceptionHandler下方法返回值类型支持多种,常见的ModelAndView,@ResponseBody注解标注,ResponseEntity等类型都OK.

源码分析介绍

原理说明-doDispatch

代码片段位于:org.springframework.web.servlet.DispatcherServlet#doDispatch

执行**@RequestMapping方法抛出异常后,Spring框架 try-catch的方法捕获异常, 正常逻辑发不发生异常都会走processDispatchResult**流程 ,区别在于异常的参数是否为null .

	HandlerExecutionChain mappedHandler = null;
	Exception dispatchException = null;
	ModelAndView mv = null;
    try{
        //根据请求查找handlerMapping找到controller
        mappedHandler=getHandler(request); 
        //找到处理器适配器HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); 
        if(!mappedHandler.applyPreHandle(request,response)){ 
            //拦截器preHandle
            return ;
        }      
        //调用处理器适配器执行@RequestMapping方法
        mv=ha.handle(request,response); 
        //拦截器postHandle
        mappedHandler.applyPostHandle(request,response,mv);  
    }catch(Exception ex){
        dispatchException=ex;
    }
    //将异常信息传入了
    processDispatchResult(request,response,mappedHandler,mv,dispatchException) 

原理说明-processDispatchResult

代码片段位于:org.springframework.web.servlet.DispatcherServlet#processDispatchResult

如果 @RequestMapping 方法抛出异常,拦截器的postHandle方法不执行,进入processDispatchResult,判断入参dispatchException,不为null , 代表发生异常,调用processHandlerException处理。

原理说明-processHandlerException

代码片段位于:org.springframework.web.servlet.DispatcherServlet#processHandlerException
在这里插入图片描述

this当前对象指dispatchServlet,handlerExceptionResolvers可以看到三个HandlerExceptionResolver,这三个是Spring框架帮我们注册的,遍历有序集合handlerExceptionResolvers,调用接口的resolveException方法。

在这里插入图片描述

注册的第一个HandlerExceptionResolver.ExceptionHandlerExceptionResolver, 继承关系如下面所示。

在这里插入图片描述

原理说明-AbstractHandlerExceptionResolver

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#resolveException

在这里插入图片描述
这里AbstractHandlerExceptionResolver的shouldApplyTo都返回true, logException用来记录日志、prepareResponse方法,用来设置response的Cache-Control。
在这里插入图片描述
异常处理方法就位于doResolveException
在这里插入图片描述
在这里插入图片描述

注意:AbstractHandlerExceptionResolver和AbstractHandlerMethodExceptionResolver名字看起来非常相似,但是作用不同,一个是面向整个类的,一个是面向方法级别的。

原理说明-AbstractHandlerMethodExceptionResolver

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#shouldApplyTo

接口方法实现AbstractHandlerExceptionResolver的resolveException,先判断shouldApplyTo,AbstractHandlerExceptionResolver 和子类AbstractHandlerMethodExceptionResolver都实现了shouldApplyTo方法,子类的shouldApplyTo都调用父类AbstractHandlerExceptionResolver的shouldApplyTo.

父类AbstractHandlerExceptionResolver的shouldApplyTo方法.

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#shouldApplyTo

Spring初始化的时候并没有额外配置 , 所以mappedHandlers和mappedHandlerClasses都为null, 可以在这块扩展进行筛选 ,AbstractHandlerExceptionResolver提供了setMappedHandlerClasses 、setMappedHandlers用于扩展。

doResolveException

代码片段位于:org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver#doResolveException
在这里插入图片描述
Spring请求方法执行一样的处理方式,设置argumentResolvers、returnValueHandlers,之后进行调用异常处理方法。
在这里插入图片描述

获取@ExceptionHandler

@ExceptionHandler的方法入参支持:Exception ;SessionAttribute 、 RequestAttribute注解、 HttpServletRequest 、HttpServletResponse、HttpSession。
在这里插入图片描述
@ExceptionHandler方法返回值常见的可以是: ModelAndView 、@ResponseBody注解、ResponseEntity。

getExceptionHandlerMethod方法

getExceptionHandlerMethod说明: 获取对应的@ExceptionHandler方法,封装成ServletInvocableHandlerMethod返回。

exceptionHandlerCache是针对Controller层面的@ExceptionHandler的处理方式,而exceptionHandlerAdviceCache是针对@ControllerAdvice的处理方式. 这两个属性都位于ExceptionHandlerExceptionResolver中。

在这里插入图片描述

ExceptionHandlerMethodResolver,缓存A之前没存储过Controller的class ,所以新建一个ExceptionHandlerMethodResolver 加入缓存中,ExceptionHandlerMethodResolver 的初始化工作一定做了某些工作。
在这里插入图片描述

resolveMethod方法

根据异常对象让 ExceptionHandlerMethodResolver 解析得到 method , 匹配到异常处理方法就直接封装成对象 ServletInvocableHandlerMethod ; 就不会再去走@ControllerAdvice里的异常处理器了,这里说明了。

在这里插入图片描述

resolveMethodByExceptionType根据当前抛出异常寻找 匹配的方法,并且做了缓存,以后遇到同样的异常可以直接走缓存取出
在这里插入图片描述
resolveMethodByExceptionType方法,尝试从缓存A:exceptionLookupCache中根据异常class类型获取Method ,初始时候肯定缓存为空 ,就去遍历ExceptionHandlerMethodResolver的mappedMethods(上面提及了key为异常类型,value为method,exceptionType为当前@RequestMapping方法抛出的异常,判断当前异常类型是不是@ExceptionHandler中value声明的子类或本身,满足条件就代表匹配上了;
在这里插入图片描述

可能存在多个匹配的方法,使用ExceptionDepthComparator排序,排序规则是按照继承顺序来(继承关系越靠近数值越小,当前类最小为0,顶级父类Throwable为int最大值),排序之后选取继承关系最靠近的那个,并且ExceptionHandlerMethodResolver的exceptionLookupCache中,key为当前抛出的异常,value为解析出来的匹配method.

全局级别异常处理器实现HandlerExceptionResolver接口

public class MyHandlerExceptionResolver implements HandlerExceptionResolver {

    @Override

    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",ex.getMessage());
        return new ModelAndView("error",mmp);
    }
}
  • 使用方式: 只需要将该Bean加入到Spring容器,可以通过Xml配置,也可以通过注解方式加入容器;
    方法返回值不为null才有意义,如果方法返回值为null,可能异常就没有被捕获.

  • 缺点分析:比如这种方式全局异常处理返回JSP、velocity等视图比较方便,返回json或者xml等格式的响应就需要自己实现了.如下是我实现的发生全局异常返回JSON的简单例子.

public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        System.out.println("发生全局异常!");
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",ex.getMessage());
        response.addHeader("Content-Type","application/json;charset=UTF-8");
        try {
            new ObjectMapper().writeValue(response.getWriter(),ex.getMessage());
            response.getWriter().flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

全局级别异常处理器@ControllerAdvice+@ExceptionHandler使用方法

用法说明:这种情况下 @ExceptionHandler与第一种方式用法相同,返回值支持ModelAndView,@ResponseBody等多种形式。

@ControllerAdvice
public class GlobalController {
    @ExceptionHandler(RuntimeException.class)
    public ModelAndView fix1(Exception e){
        System.out.println("全局的异常处理器");
        ModelMap mmp=new ModelMap();
        mmp.addAttribute("ex",e);
        return new ModelAndView("error",mmp);
    }
}
  • 方式一:提到ExceptionHandlerExceptionResolver不仅维护@Controller级别的@ExceptionHandler,同时还维护的@ControllerAdvice级别的@ExceptionHandler代码片段位于:
    在这里插入图片描述
    isApplicableToBeanType方法是用来做条件判断的,@ControllerAdvice注解有很多属性用来设置条件,
    basePackageClasses、assignableTypes、annotations等,比如我限定了annotations为注解X, 那标注了@X 的ControllerA就可以走这个异常处理器,ControllerB就不能走这个异常处理器。

现在问题的关键就只剩下了exceptionHandlerAdviceCache是什么时候扫描@ControllerAdvice的,下面的逻辑和@ExceptionHandler的逻辑一样了,exceptionHandlerAdviceCache初始化逻辑:

代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#afterPropertiesSet,afterPropertiesSet是Spring bean创建过程中一个重要环节。
在这里插入图片描述


代码片段位于:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache

在这里插入图片描述

ControllerAdviceBean.findAnnotatedBeans方法查找了SpringMvc父子容器中标注 @ControllerAdvice 的bean, new ExceptionHandlerMethodResolver初始化时候解析了当前的@ControllerAdvice的bean的@ExceptionHandler,加入到ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,key为ControllerAdviceBean,value为ExceptionHandlerMethodResolver . 到这里exceptionHandlerAdviceCache就初始化完毕。

Spring父子容器中所有@ControllerAdivce的bean的方法

代码片段位于:org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans

遍历了SpringMVC父子容器中所有的bean,标注ControllerAdvice注解的bean加入集合返回。

比较说明

@Controller+@ExceptionHandler、HandlerExceptionResolver接口形式、@ControllerAdvice+@ExceptionHandler优缺点说明:

调用优先级

  • @Controller+@ExceptionHandler优先级最高
  • @ControllerAdvice+@ExceptionHandler 略低
  • HandlerExceptionResolver最低。

三种方式并存的情况 优先级越高的越先选择,而且被一个捕获处理了就不去执行其他的

三种方式都支持多种返回类型

  • @Controller+@ExceptionHandler、@ControllerAdvice+@ExceptionHandler可以使用Spring支持的@ResponseBody、ResponseEntity。

  • HandlerExceptionResolver方法声明返回值类型只能是 ModelAndView,如果需要返回JSON、xml等需要自己实现.。

缓存利用

  • @Controller+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerCache,@ControllerAdvice+@ExceptionHandler的缓存信息在ExceptionHandlerExceptionResolver的exceptionHandlerAdviceCache中,

  • HandlerExceptionResolver接口是不做缓存的,在异常报错的情况下才会走自己的HandlerExceptionResolver实现类,多少有点性能损耗.

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

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

相关文章

ONNX转NCNN记录

【pytorch 转 onnx】pytorch-onnx 【onnx 转 ncnn】onnx-ncnn 【ncnn 加载模型】ncnn-load 一、python安装依赖项 pip install onnxruntime onnx opencv-python 二、创建模型并训练&#xff0c;加载模型参数并输出onnx #### pytorch 转 onnx import torch import torch.onnx…

小白学Pytorch系列- -torch.distributions API Distributions (1)

小白学Pytorch系列- -torch.distributions API Distributions (1) 分布包包含可参数化的概率分布和抽样函数。这允许构造用于优化的随机计算图和随机梯度估计器。这个包通常遵循TensorFlow分发包的设计。 不可能通过随机样本直接反向传播。但是&#xff0c;有两种主要方法可以…

【华为机试真题详解JAVA实现】—矩阵乘法

目录 一、题目描述 二、解题代码 一、题目描述 如果A是个x行y列的矩阵,B是个y行z列的矩阵,把A和B相乘,其结果将是另一个x行z列的矩阵C。这个矩阵的每个元素是由下面的公式决定的 矩阵的大小不超过100*100 输入描述: 第一行包含一个正整数x,代表第一个矩阵的行数 第二行…

APP测试弱网测试

1、为什么要做弱网测试 当前APP网络环境比较复杂&#xff0c;网络制式有2G、3G、4G网络&#xff0c;还有越来越多的公共Wi-Fi。不同的网络环境和网络制式的差异&#xff0c;都会对用户使用app造成一定影响。 另外&#xff0c;当前app使用场景多变&#xff0c;如进地铁、上公交、…

【Linux】进程理解与学习Ⅳ-进程地址空间

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;相关文章推荐&#xff1a;【Linux】冯.诺依曼体系结构与操作系统【Linux】进程理解与学习Ⅰ-进程概念浅谈Linux下的shell--BASH【Linux】进程理解与学习…

跟姥爷深度学习1 浅用tensorflow做个天气预测

一、前言 最近人工智能、深度学习又火了&#xff0c;我感觉还是有必要研究一下。三年前浅学了一下原理没深入研究框架&#xff0c;三年后感觉各种框架都成熟了&#xff0c;现成的教程也丰富了&#xff0c;所以我继续边学边写。原教程链接&#xff1a; 第一章&#xff1a;tens…

Linux- 系统随你玩之--玩出花活的命令浏览器下

文章目录1、背景2、常规操作2.1、测试相关2.1.1、修改 HTML 请求标头2.1.2、 模拟不同浏览器发出2.1.3、重定向2.2、 下载相关操作2.2.1、 后台下载2.2.2、设置下载重试次数2.2.3、过滤指定格式下载2.2.4、限制总下载文件大小2.2.5、匿名FTP下载2.2.6、FTP认证下载2.2.7、利用代…

(链表专题) 725. 分隔链表 ——【Leetcode每日一题】

725. 分隔链表 给你一个头结点为 head 的单链表和一个整数 k &#xff0c;请你设计一个算法将链表分隔为 k 个连续的部分。 每部分的长度应该尽可能的相等&#xff1a;任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。 这 k 个部分应该按照在链表中出现的顺…

亚马逊 CodeWhisperer: 个人免费的类似GitHubCopilot能代码补全的 AI 编程助手

1、官网 AI Code Generator - Amazon CodeWhisperer - AWS 官方扩展安装教程 2、安装VSCode 下载安装VSCode 3、VSCode安装CodeWhisperer插件 安装VSCode插件 - AWS Toolkit主侧栏&#xff0c;点击AWS &#xff0c;展开CodeWhisperer&#xff0c;点击Start 在下拉菜单中点…

【100个 Unity实用技能】 | C# 中关于补位的写法 PadLeft,PadRight 函数

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…

LeetCode_101

内容提要 贪心算法 保证每次操作都属局部最优的&#xff0c;从而使得最后的结果是全局最优 全局结果是局部结果的简单求和&#xff0c;且局部结果互不相干 分配问题 分发饼干 455 简单 分发糖果 135 困难 先从左往右遍历一遍&#xff0c;如果右边孩子的评分比左边的高…

TryHackMe-Year of the Jellyfish(linux渗透测试)

Year of the Jellyfish 请注意 - 此框使用公共 IP 进行部署。想想这对你应该如何应对这一挑战意味着什么。如果您高速枚举公共 IP 地址&#xff0c;ISP 通常会不满意… 端口扫描 循例nmap 扫描结果中还有域名&#xff0c;加进hosts FTP 枚举 尝试anonymous Web枚举 有三个端…

LoRa无线通信技术之CAD介绍

信道活动检测 Lora扩频调制技术的使用在确定信道是否已被可能低于接收机噪声底限的信号。在这种情况下使用常规的RSSI方式判断显然是不切实际的。为此,信道活动检测器用于检测其他LoRaTM信号的存在。下图为通道活动检测(CAD)过程: 工作原理 Lora信道活动检测模式被设计成以最…

一站式指标平台 Kyligence Zen 功能详解

近日&#xff0c;Kyligence 正式发布一站式指标平台 Kyligence Zen GA 版本。其基于 Kyligence 核心 OLAP 能力打造&#xff0c;融合了领先企业建设指标平台的丰富实践&#xff0c;具备 ZenML 指标语言、指标目录、Excel / WPS 直连分析、模板市场等创新能力&#xff0c;将以简…

GPU受限,国内AI大模型能否交出自己的答卷?

继百度之后&#xff0c;阿里、华为、京东、360等大模型也陆续浮出水面&#xff0c;大模型军备竞赛正式开启。 4月7日&#xff0c;阿里云宣布自研大模型“通义千问”开始邀请企业用户测试体验。 4月8日&#xff0c;华为云人工智能领域首席科学家田奇现身《人工智能大模型技术高峰…

一起学 WebGL:图元的类型

大家好&#xff0c;我是前端西瓜哥&#xff0c;今天来说说 WebGL 中的三种图元。 在 WebGL 中&#xff0c;图元有三种&#xff1a;点、线、以及三角形。 绘制的 API 为&#xff1a; gl.drawArrays(mode, first, count)这里的 mode 就是要绘制的图元类型。 我们绘制 4 个点&…

办公协作效率想提质增效,可借助开源大数据工具!

在信息爆炸式发展的今天&#xff0c;提升办公协作效率&#xff0c;让各部门的信息有效互通起来&#xff0c;做好数据管理&#xff0c;已经成为众企业提升竞争力的方式方法。那么&#xff0c;如果想要提升办公效率&#xff0c;就需要了解开源大数据工具了。在数字化发展进程中&a…

HTTP协议概述 | 简析HTTP请求流程 | HTTP8种请求方法

目录 &#x1f30f; HTTP的简单介绍 何为HTTP HTTP1.0与HTTP1.1 &#x1f30f; HTTP的请求方法 1、OPTIONS 2、HEAD 3、GET 4、POST 5、PUT 6、DELETE 7、TRACE 8、CONNECT &#x1f30f; HTTP的工作原理 &#x1f30f; HTTP请求/响应的步骤 1、客户端连接到Web…

AI 芯片的简要发展历史

随着人工智能领域不断取得突破性进展。作为实现人工智能技术的重要基石&#xff0c;AI芯片拥有巨大的产业价值和战略地位。作为人工智能产业链的关键环节和硬件基础&#xff0c;AI芯片有着极高的技术研发和创新的壁垒。从芯片发展的趋势来看&#xff0c;现在仍处于AI芯片发展的…

【案例教程】基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作实践技术

【原文链接】: 基于R语言、MaxEnt模型融合技术的物种分布模拟、参数优化方法、结果分析制图与论文写作实践技术https://mp.weixin.qq.com/s?__bizMzU5NTkyMzcxNw&mid2247537049&idx3&sn31ef342c4808aed6fee6ac108b899a33&chksmfe6897f3c91f1ee5c4fa8e4eeea34…