所以spring mvc异常处理工作原理是啥

文章目录

  • spring mvc异常处理(源码分析)
    • 概述
    • 原理(源码角度)模拟debug
      • 前期提要
      • 分析
      • 4个map
      • 4个map的初始化
      • 为什么需要基于mappedMethods缓存
    • 总结一下

spring mvc异常处理(源码分析)

概述

spring mvc有下面三种方式实现异常处理:

分别是:

  • 实现handlerExceptionResolver+@Component(上古版本)
  • controller里耦合@ExceptionHandler(优先级最高)
  • @ControllerAdvice+@ExceptionHandler(最常用)

1.在对应类实现spring的异常处理核心组件handlerExceptionResolver+@Component(在多个异常执行时的优先级最低,并且麻烦,最早期的异常处理)

在这里插入图片描述

@ExceptionHandler注解在controller方法上(优先级高于ControllerAdvice,但比较麻烦)

在这里插入图片描述

@ControllerAdvice+@ExceptionHandler统一异常处理(最常用)

在这里插入图片描述

原理(源码角度)模拟debug

从源码角度分析,spring mvc是如何进行统一异常处理的?

为了更好的说明问题,我选择用倒推,先去模拟错误发生

前期提要

1.我定义了一个controller,有一个deleteBook方法,在这个方法中会对传来的bookId检查,非法则会抛出一个runtimeException

在这里插入图片描述

2.我用上述三种spring mvc异常处理方法都加在了项目中:如概述的图片所示

3.向deleteBook发送一个id非法的请求。

4.在processHandlerException方法处打上断点。

分析

可以看到这里马上去执行resolveException(这正是spring mvc的resolveException方法,若用上古处理方法,你需要去重写它。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

而在执行resolveException,会依次走更抽象的顶层方法,然后来到核心方法doResolveHandlerMethodException

在这里插入图片描述

doResolveHandlerMethodException方法中:他会干下面的事情

  • 1.根据HandlerMethod(要处理的方法)和exception获取异常处理的Method(先从exceptionHandlerCache查,再从advice中查)

  • 2.设置异常处理方法的参数解析器和返回值解析器(一般就是之前默认的argumentResolvers和returnValueHandlers)

  • 3.执行具体的异常处理方法(也就是1找到的Method)

  • 4.对返回的视图模型进行处理(这个不用太纠结,因为一般不返回视图了,并且一般在@ExceptionHandler标注的方法中我们会对请求处理的,所以一般会返回new ModelAndView(),表示不对视图进行进一步处理)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

重点就是如何根据HandlerMethod(要处理的方法)和exception获取异常处理的Method

先理解一下exceptionHandlerCache和exceptionHandlerAdviceCache。因为Method就从他们身上获取的。

4个map

exceptionHandlerCache,exceptionHandlerAdviceCache,mappedMethods,基于mappedMethods的缓存

直接看定义

	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache =new ConcurrentHashMap<>(64);private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<>();

exceptionHandlerCache存储@RequestMapping对应的ExceptionHandlerMethodResolver(就是本例中在@controller里的@ExceptionHandler)

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

exceptionHandlerAdviceCache保存了@ControllerAdvice对应的ExceptionHandlerMethodResolver(在本例中就是global那个类)

还有mappedMethods,封装的异常对应的处理方法。和基于它的mappedMethods缓存

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

为什么需要这个缓存后面说

回到如何根据HandlerMethod(要处理的方法)和exception获取异常处理的Method这个问题上来

直接看详细注释:

@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;if (handlerMethod != null) {//0.获取handlerMethod(deleteBook)的类(BookController)handlerType = handlerMethod.getBeanType();//1.从exceptionHandlerCache查(@Controller里的@ExpectionHandler)查一个resolverExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);//2.第一次一般为null,这时会根据handlerType去new一个resolver,若有handlerType对应的异常方法,这时这个resolver的mappedMethods有这个方法if (resolver == null) {resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}//3.根据异常解析这个方法,先从mappedMethods的缓存中查,查不出到mappedMethods查,然后写入缓存Method method = resolver.resolveMethod(exception);if (method != null) {//4.解析出方法了,在这个ServletInvocableHandlerMethod会回调具体方法return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext);}// For advice applicability check below (involving base packages, assignable types// and annotation presence), use target class instead of interface-based proxy.if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}//5.和上面类似,只是遍历所有的exceptionHandlerAdviceCache(@ControllerAdvice),从每一个entry里找出resolver,试图解析出exception对应的方法,然后调用for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();if (advice.isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);}}}return null;
}

经过测试,在本例中,

若存在三种异常处理方式

第一次执行:走234

第二次执行:走1(exceptionHandlerCache已被写入)

若把@Controller耦合@ExceptionHandler去掉,则走advice的cache(也就是global)。

若把global也去掉,则走最后那个上古版本的resolver,不会到这个getExceptionHandlerMethod方法的。直接执行resolveException了

所以优先级:@Controller耦合@ExceptionHandler > @ControllerAdvice+@ExceptionHandler > 实现handlerExceptionResolver+@Component(上古版本)

4个map的初始化

上面在探究如何根据HandlerMethod(要处理的方法)和exception获取异常处理的Method中,你会好奇这些map什么时候被初始化的

先说结论:

  • exceptionHandlerAdviceCache在ExceptionHandlerExceptionResolver被初始化的过程的afterPropertiesSet方法中赋值

  • exceptionHandlerCache则是在执行异常时,碰到了一场异常要处理了,再去初始化(也就是第二次执行中exceptionHandlerCache被写入)

handlerExceptionResolver作为bean注入容器:

spring mvc通过 WebMvcConfigurationSupport类(configuration类)+@Bean注解的方式来注入handlerExceptionResolver bean

并在上述的#addDefaultHandlerExceptionResolvers方法中,注册了3个处理器:分别处理@ExceptionHandler,@ResponseStatus标注的方法和默认异常解析器。

@Bean
public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();//钩子方法,用于向列表中添加用户自定义的异常处理器。configureHandlerExceptionResolvers(exceptionResolvers);//向列表中添加Spring MVC的默认异常处理器if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}//扩展或修改异常处理器列表extendHandlerExceptionResolvers(exceptionResolvers);//返回一个HandlerExceptionResolverComposite实例,它是handlerExceptionResolver一种实现HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;
}

在addDefaultHandlerExceptionResolvers

protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers,ContentNegotiationManager mvcContentNegotiationManager) {ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();//设置内容协商处理器(确定相应格式),消息转换器(java<->json),exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());//若jackson存在,则给ResponseBodyAdvice设置一个JsonViewResponseBodyAdvice实例,用于处理jackson的jsonviewif (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}if (this.applicationContext != null) {exceptionHandlerResolver.setApplicationContext(this.applicationContext);}//调用afterPropertiesSet,完成初始化exceptionHandlerResolver.afterPropertiesSet();exceptionResolvers.add(exceptionHandlerResolver);ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);exceptionResolvers.add(responseStatusResolver);exceptionResolvers.add(new DefaultHandlerExceptionResolver());//添加3个ExceptionResolver:分别处理@ExceptionHandler,@ResponseStatus,默认异常解析器
}

在exceptionHandlerResolver.afterPropertiesSet()中:

1.找到所有@ControllerAdvice注解的类,注册为bean

2.将每一个@ControllerAdvice注解的类与ExceptionHandlerMethodResolver的对应关系 写入exceptionHandlerAdviceCache中。

3.初始化argumentResolvers和returnValueHandlers(一般就是spring默认提供的各种processor和handler)

如returnValueHandlers的HttpEntityMethodProcessor,ModelAndViewMethodReturnValueHandler等等。

如argumentResolvers的**@SessionAttribute**,@RequestAttribute

private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}//获取所有@ControllerAdvice的类List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}if (logger.isDebugEnabled()) {int handlerSize = this.exceptionHandlerAdviceCache.size();int adviceSize = this.responseBodyAdvice.size();if (handlerSize == 0 && adviceSize == 0) {logger.debug("ControllerAdvice beans: none");}else {logger.debug("ControllerAdvice beans: " +handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice");}}
}

//RequestMappingHandlerAdapter.java
@Override
public void afterPropertiesSet() {// Do this first, it may add ResponseBody advice beansinitControllerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.initBinderArgumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.returnValueHandlers == null) {List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);}
}

https://www.cnblogs.com/java-chen-hao/p/11190659.html#_label0

https://blog.csdn.net/qq_26222859/article/details/51320493

https://blog.csdn.net/zzti_erlie/article/details/105746203

为什么需要基于mappedMethods缓存

因为一个异常可能有多个匹配方法,这时需要按照继承优先级对它进行排序,执行最近的那个。

由于最后只执行最近的那个method,那么我们只关心这个method就OK,所以将最近的这个method写入一个map,在每次查询时直接去查这个缓存并返回就好了,避免了排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还是不会写源码分析,小小总结一下

总结一下

  • spring mvc有下面三种方式实现异常处理:

    • 实现handlerExceptionResolver+@Component(上古版本)

    • controller里耦合@ExceptionHandler(优先级最高)

    • @ControllerAdvice+@ExceptionHandler(最常用)

  • 若存在三种异常处理时,优先级为:@Controller耦合@ExceptionHandler > @ControllerAdvice+@ExceptionHandler > 实现handlerExceptionResolver+@Component(上古版本)(执行一次就ok)

    • 为什么:先查ExceptionHandler的cache,尝试解析一个方法,解析不出来再去查advice的cache。
  • spring mvc异常处理如何实现的

    • 当打开webMvcConfiguration后,会注入handlerExceptionResolver(一个bean),这个handlerExceptionResolver会注入@ExceptionHandler,@ResponseStatus,默认异常解析器3个处理器,最值得关注的是ExceptionHandler处理器。
    • 对于ExceptionHandler的处理器,会调用afterPropertiesSet方法完成initExceptionHandlerAdviceCache的注入
    • 当异常发生后,在执行resolveException会一层一层到doResolveHandlerMethodException方法,在这里寻找该异常匹配的方法,并回调。(完成类似aop切面的效果)。
  • 为什么mappedMethods需要缓存

    • 因为一个异常可能有多个匹配方法,这时需要按照继承优先级对它进行排序,执行最近的那个。
    • 由于最后只执行最近的那个method,所以将最近的这个method写入一个map,在每次查询时直接去查这个缓存并返回就好了,避免了排序
      onHandler处理器。
    • 对于ExceptionHandler的处理器,会调用afterPropertiesSet方法完成initExceptionHandlerAdviceCache的注入
    • 当异常发生后,在执行resolveException会一层一层到doResolveHandlerMethodException方法,在这里寻找该异常匹配的方法,并回调。(完成类似aop切面的效果)。
  • 为什么mappedMethods需要缓存

    • 因为一个异常可能有多个匹配方法,这时需要按照继承优先级对它进行排序,执行最近的那个。
    • 由于最后只执行最近的那个method,所以将最近的这个method写入一个map,在每次查询时直接去查这个缓存并返回就好了,避免了排序

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

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

相关文章

力扣每日一题 6/18 字符串/模拟

博客主页&#xff1a;誓则盟约系列专栏&#xff1a;IT竞赛 专栏关注博主&#xff0c;后期持续更新系列文章如果有错误感谢请大家批评指出&#xff0c;及时修改感谢大家点赞&#x1f44d;收藏⭐评论✍ 2288.价格减免 【中等】 题目&#xff1a; 句子 是由若干个单词组成的字符…

‘引爆增长·赋能十堰’第一届学习峰会在十堰东方汉宫国际酒店成功举办

‘引爆增长赋能十堰’第一届学习峰会在十堰东方汉宫国际酒店成功举办 2024年6月 17 至18 日&#xff0c;为期两天的“引爆增长赋能十堰”第一届学习交流峰会在湖北十堰东方汉宫国际酒店一号盛大举行&#xff0c;学习峰会现场&#xff0c;来自十堰地区及邻边地市的上百位实体企业…

netty服务端与客户端的启动流程

如图所示&#xff0c;右侧是服务端Server&#xff0c;左侧是客户端Client 要点说明&#xff1a; 1.在Server中&#xff0c;在NioEventLoopGroup()中&#xff0c;会有1个selector和线程在不断循环&#xff0c;等待是否有accept事件&#xff0c;在accept事件发生后&#xff0c;才…

四款让人大开眼界的高质量软件,个个实力超群,使用起来爱不释手

电脑里的Windows软件&#xff0c;简直多得数不清&#xff0c;啥都有。 像那个电子表格、写文章的、玩游戏聊天的、还有修图的&#xff0c;这些都太常见了&#xff0c;它们确实给咱们生活带来方便&#xff0c;但有时候也会让那些不太懂电脑的小伙伴们头疼不已。 讲真&#xff0…

重学java 73.设计模式

本想送你一本沉思录&#xff0c;可该迷途知返的人是我 —— 24.6.18 设计模式 设计模式(Design pattern)&#xff0c;是一套被反复使用、经过分类编目的、代码设计经验的总结&#xff0c;使用设计模式是为了可重用代码、保证代码可靠性、程序的重用性,稳定性。 1995 年&#x…

全网最全 Kimi 使用手册,看完 Kimi 效率提升 80%

在当前AI文字大模型领域&#xff0c;ChatGPT4.0无疑是最强大。然而&#xff0c;最近最火爆的大模型非国产Kimi莫属。 相较于其它大模型&#xff0c;Kimi 最大的优势在于&#xff0c;超长文本输入&#xff0c;支持200万汉字&#xff0c;是全球范围内罕见的超长文本处理工具&…

在Linux系统中安装凸语言

凸语言在2023国产编程语言蓝皮书中的介绍如下&#xff1a; 凸语言gitee页面&#xff1a;凸语言: tu-lang 是一种动态类型编译型的通用编程语言, 已实现自举 (gitee.com) 使用git克隆源码&#xff1a; git clone https://github.com/tu-lang/tu.git 安装凸语言环境&#xff1a…

文件系统崩溃一致性、方法、原理与局限

前言 先提几个问题&#xff1a;什么是文件系统崩溃一致性&#xff1f;为什么会出现文件系统崩溃一致性问题&#xff1f;有哪些方法可以解这个问题&#xff1f;它们各自又有哪些局限性&#xff1f; window系统电脑异常后会蓝屏、手机死机卡顿后我们会手动给它重启&#xff0c;大…

范式(上)-第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、可用关系模式

一、范式的作用 根据关系模式间属性的数据依赖来评价关系模式的好坏 以下我们将基于函数依赖的范围内来讨论范式 二、范式的定义 1、数据依赖满足一定约束的关系模式是范式 2、范式是符合某一级别的关系模式的集合&#xff0c;关系模式R为第几范式可记为 三、第一范式&am…

APP渗透、WIFI近源渗透之透明代理下的流量分析与嗅探

APP渗透、WIFI近源渗透之透明代理下的流量分析与嗅探 原文链接&#xff1a;https://xz.aliyun.com/t/14864 前言 在攻防中对APP进行渗透时可能会遇到代理及VPN的检测&#xff0c;以及在近源渗透时可能会有WIFI钓鱼的需求&#xff0c;而透明代理是一个很好的解决方案&#xf…

Autodesk Inventor 机械三维设计软件下载安装,Inventor 专业的三维制图软件

Inventor&#xff0c;它的一大亮点在于能够将三维尺寸、标注以及尺寸公差直接融入三维模型中&#xff0c;使得这些关键信息能够无缝对接下游应用&#xff0c;极大地提升了设计流程中的连贯性和一致性。 谈及Inventor的尺寸公差功能&#xff0c;更是让人赞不绝口。在复杂的设计过…

【html】用html5+css3+JavaScript制作一个计数器

目录 简介&#xff1a; 效果图&#xff1a; 源码&#xff1a; html: CSS: JS: 源码解析&#xff1a; 简介&#xff1a; 在日常生活当中很多事情都需要用到计数器特别是在体育运动当中&#xff0c;可以我们那么我们可不可以通过网页来制作一个计数器呢答案是肯定的我们需要利…

WPF Prism框架搭建

WPF Prism框架搭建 1.引入Prism框架 在Nuget包管理器中搜索Prism&#xff0c;并添加到项目中 2.在项目中使用prism框架 2.1 修改app.xaml 删除项目中自带的StartupUri 修改Application节点为prism:PrismApplication 引入prism命名空间 <prism:PrismApplication x:C…

Java工具-实现无损png转换jpg格式

目录 1、背景说明 2、通过代码实现格式转换 3、无损转化 4、说明 读取 PNG 图像&#xff1a; 创建空的 JPG 图像&#xff1a; 绘制 PNG 图像到 JPG 图像&#xff1a; 设置 JPG 图片压缩质量&#xff1a; 写入 JPG 文件并关闭流&#xff1a; 5、jpg转png 1、背景说明 …

最新开源:英伟达Nemotron-4 340B,哔哩哔哩Index-1.9B,谷歌RecurrentGemma-9B...

文章目录 1. 英伟达开源Nemotron-4 340B2. 哔哩哔哩开源轻量级模型 Index-1.9B3. 微软开源混合模型 Samba4. 谷歌开源 RecurrentGemma-9B&#xff0c;性能与Gemma相同5. Stable Diffusion 3 Medium&#xff1a;“最强文生图开源 AI 模型” 1. 英伟达开源Nemotron-4 340B 当地时…

C++初学者指南第一步---6.枚举和枚举类

C初学者指南第一步—6.枚举和枚举类 文章目录 C初学者指南第一步---6.枚举和枚举类1.作用域的枚举(enum class类型&#xff09;&#xff08;C11&#xff09;2.无作用域的枚举(enum类型)3.枚举类的基础类型4.自定义枚举类映射5.和基础类型的互相转换 1.作用域的枚举(enum class类…

Python武器库开发-武器库篇之链接提取器(六十)

Python武器库开发-武器库篇之链接提取器&#xff08;六十&#xff09; 链接提取器介绍 链接提取器&#xff08;Link Extractor&#xff09;是一种用于从网页中提取链接的工具。它可以从网页的源代码中识别出所有的链接&#xff0c;并将这些链接提取出来。链接提取器可以用于各…

Python第二语言(十二、SQL入门和实战)

目录 1. Python中使用MySQL 1.1 pymysql第三方库使用MySQL 1.2 连接MySQL 1.3 操作数据库&#xff0c;创建表 1.4 执行查询数据库语句 2. python中MySQL的插入语句 2.1 commit提交 2.2 自动提交 3. pymysql案例 3.1 数据内容 3.2 DDL定义 3.3 实现步骤 3.4 文件操…

用python克隆了前男友的声音

声音克隆开源项目推荐&#xff1a;MockingBird 项目简介 MockingBird 是一个由开源社区开发的声音克隆项目&#xff0c;托管在 GitHub 上。该项目旨在通过深度学习技术实现高质量的声音克隆&#xff0c;使用户能够合成任意人的声音&#xff0c;并生成自然、流畅的语音输出。M…

【2024最新华为OD-C/D卷试题汇总】[支持在线评测] 单词大师(100分) - 三语言AC题解(Python/Java/Cpp)

&#x1f36d; 大家好这里是清隆学长 &#xff0c;一枚热爱算法的程序员 ✨ 本系列打算持续跟新华为OD-C/D卷的三语言AC题解 &#x1f4bb; ACM银牌&#x1f948;| 多次AK大厂笔试 &#xff5c; 编程一对一辅导 &#x1f44f; 感谢大家的订阅➕ 和 喜欢&#x1f497; &#x1f…