所以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)查一个resolver
      ExceptionHandlerMethodResolver 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的jsonview
	if (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 beans
   initControllerAdviceCache();

   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,在每次查询时直接去查这个缓存并返回就好了,避免了排序

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

压测怎么做?如何自动化?盘点各大公司全链路压测方案与实践

本文综合盘点各大公司团队的全链路压测技术方案和实践路径&#xff0c;供大家参考。 一、什么是全链路压测&#xff1f; 全链路压测指的是基于实际的生产业务场景、系统环境&#xff0c;模拟海量的用户请求和数据对整个业务链进行压力测试&#xff0c;并持续调优的过程。常用…

30v-180V降3.3V100mA恒压WT5107

30v-180V降3.3V100mA恒压WT5107 WT5107是一款恒压单片机供电芯片&#xff0c;它可以30V-180V直流电转换成稳定的3.3V直流电&#xff08;最大输出电流300mA&#xff09;&#xff0c;为各种单片机供电。WT5107的应用也非常广泛。它可以用于智能家居、LED照明、电子玩具等领域。比…

功能强大的API函数FindFirstFile使用介绍(附源码)

在处理文件的相关代码中,会频繁使用到Windows系统API函数FindFirstFile,这个函数功能很强大,很多功能都不开它。本文就根据我们在项目中使用该函数的情况,来大概地梳理一下使用FindFirstFile都可以实现哪些常用的功能。 1、FindFirstFile函数声明与WIN32_FIND_DATA结构体 我…

Java的核心类库

引言 在Java编程中&#xff0c;熟练掌握常用类与对象操作是开发的基础。Java的核心类库提供了丰富的功能&#xff0c;可以帮助开发者高效地处理各种编程任务。本文将详细介绍Java字符串操作、集合框架、日期与时间处理等内容&#xff0c;并通过图表和表格进行总结与示范。 字符…

Selenium IED-控制已打开的Chrome浏览器

本文已收录于专栏 《自动化测试》 目录 背景介绍优势特点操作步骤总结提升 背景介绍 在我们进行自动化测试的过程中有时候会遇见一个很棘手的问题那就是登录的过程中需要图片验证码&#xff0c;图片验证码设计的初衷其实就是为了防自动化&#xff0c;防止一些人利用自动工具恶意…

B-splines曲线的绘制(Matlab)

虽然在这个链接三次 Bspline(B样条曲线) NURBS曲线的绘制 matlab_三次b样条曲线的绘制-CSDN博客中我们介绍了NURBS曲线&#xff0c;然而有时候我们通过B-spline曲线也能够解决问题。B-spline曲线作为NURBS曲线的一种特例&#xff0c;这里给出均匀B-spline曲线的表达式&#xff…

oracle12c到19c adg搭建(三)oracle19c数据库软件安装

由于这里的19c是做备库所以我们只安装软件不用创建实例&#xff0c;实例由主库同步过来 解压软件到安装目录 注意19c得db要直接解压到19c得软件安装目录 [rooto12u19p software]# ls V982063-01.zip [rooto12u19p software]# ls -ll total 2987996 -rw-r--r-- 1 root ro…

阿里云服务器无法远程登录连接:操作系统禁用了密码登录方式,会导致使用了正确的用户名和密码仍无法登录

阿里云服务器无法远程登录连接&#xff1a;操作系统禁用了密码登录方式&#xff0c;会导致使用了正确的用户名和密码仍无法登录 报错信息报错原因解决办法 报错信息 Workbench密码登录 登录失败 操作系统禁用了密码登录方式&#xff0c;会导致使用了正确的用户名和密码仍无法登…

充电学习—7、BC1.2 PD协议

BC1.2&#xff08;battery charging&#xff09;充电端口识别机制&#xff1a; SDP、CDP、DCP 1、VBUS detect&#xff1a;vbus检测 PD&#xff08;portable device&#xff0c;便携式设备&#xff09;中有个检测VBUS是否有效的电路&#xff0c;电路有个参考值&#xff0c;高…

Python-自动化运维-安装初始化

Python-自动化运维-安装初始化 第一部分-环境安装 第一部分-环境安装 专业版白嫖地址&#xff1a;https://signup.live.com/signup python环境&#xff08;windows&#xff09; pycharm环境&#xff08;windows&#xff09; 虚拟机&#xff08;ubantu/contos&#xff09; 开…

电脑丢失dll文件一键修复的方法有哪些?分析dll文件修复的多种策略

我们经常会遇到各种各样的问题&#xff0c;其中之一就是DLL文件的丢失。DLL文件&#xff08;动态链接库&#xff09;是操作系统和应用程序正常运行所必需的文件&#xff0c;当这些文件丢失或损坏时&#xff0c;可能会导致软件无法正常启动&#xff0c;甚至影响系统的稳定性。对…

深入探究RTOS的任务调度

阅读引言&#xff1a; 此文将会从一个工程文件&#xff0c; 一步一步的分析RTOS的任务调度实现&#xff0c; 这里选用FreeRTOS分析&#xff0c; 别的也差不多的&#xff0c; 可能在细节上有少许不一样。 目录 1&#xff0c; 常见嵌入式实时操作系统 2&#xff0c; 任务调度的…

鸿蒙原生应用元服务开发-位置服务申请权限

申请位置权限开发指导 场景概述 应用在使用位置服务系统能力前&#xff0c;需要检查是否已经获取用户授权访问设备位置信息。如未获得授权&#xff0c;可以向用户申请需要的位置权限。 系统提供的定位权限有&#xff1a; ohos.permission.LOCATION&#xff1a;用于获取精准位置…

Docker配置阿里云加速器(2续)

默认情况下镜像是从docker hub下载&#xff0c;由于docker hub服务器在国外&#xff0c;由于网络原因镜像下载速度较慢&#xff0c;一般会配置镜像加速进行下载 国内镜像加速器有阿里云、网易云、中科大等&#xff0c;本章配置阿里云镜像加速器&#xff0c;速度较快 镜像加速源…

miniconda安装教程以及pip换源【Windows版本】

Anaconda包含内容较多&#xff0c;这边采用miniconda进行安装演示。 下载安装包 官网链接&#xff1a;https://docs.anaconda.com/miniconda/ 蓝奏云加速链接&#xff1a;https://wwt.lanzoue.com/i6ts3225vuef 开始安装 配置conda 在Windows开始菜单中&#xff0c;找到刚安…

监控局域网电脑屏幕的办法,最简单的三种方法,好用!

在现代企业管理和家庭教育环境中&#xff0c;对局域网内电脑屏幕进行有效监控成为了保障信息安全、提升工作效率和监督行为规范的重要手段。 监控局域网电脑屏幕不仅可以帮助管理者了解员工的工作状态&#xff0c;确保资源的合理使用&#xff0c;还能在一定程度上预防潜在的网…

storelibtest工具使用

1.介绍 服务器上使用broadcom raid卡&#xff0c;不可避免要使用到storelib代码。这一部分是由ami完整提供或者博通提供代码自行编译集成到ast2500/ast2600平台BMC中. 针对一般性的开发&#xff0c;依托ami代码&#xff0c;然后集成开发功能。但是如果是新功能导入而ami还未导…

【css】如何修改input选中历史选项后,自动填充的蓝色背景色

自动填充前&#xff1a; 自动填充后&#xff1a; 解决办法 方法一&#xff1a;设置背景透明 改变input自动填充背景颜色 // 通过拉长过渡时间&#xff0c;和延迟过渡开始时间&#xff0c;掩盖input自动填充背景颜色input:-internal-autofill-previewed,input:-internal-aut…

基于SpringBoot+Vue企业会议室预定管理系统设计和实现

基于SpringBootVue企业会议室预定管理系统设计和实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种定制系统 …

一招教你房间内灰尘多怎么处理?除粉尘好用的空气净化器分享

在你吸尘、扫地、擦家具的时候&#xff0c;你或许会奇怪&#xff0c;为什么灰尘每天擦&#xff0c;每天有&#xff1f;即使门窗关得好好的&#xff0c;过几天还是会落上一层薄薄的灰。它们究竟是什么&#xff1f;对我们的健康又有什么影响呢&#xff1f;我们每天生活在房屋中&a…