Java全局异常处理,@ControllerAdvice异常拦截原理解析【简单易懂】

https://www.bilibili.com/video/BV1sS411c7Mo


在这里插入图片描述


文章目录

  • 一、全局异常处理器的类型
    • 1-1、实现方式一
    • 1-2、实现方式二
  • 二、全局异常拦截点
    • 2-1、入口
    • 2-2、全局异常拦截器是如何注入到 DispatcherServlet 的
  • 三、ControllerAdvice 如何解析、执行
    • 3-1、解析
    • 3-2、执行
  • 四、其它
    • 4-1、设置HTTP状态码
    • 4-2、异常处理器排序
    • 4-3、所谓全局异常


最近在做系统升级的时候,引发了一个BUG,原本系统是有一个异常处理器A,引入了某个底包中也带了一个异常处理器B,最终走了底包的异常处理器B。 A对于异常的时候会返回HTTP状态码为500,B对于异常处理器返回的HTTP状态码为200,前端基于HTTP状态码进行提示的,就出了问题

本篇文章我们就来讨论一下在JavaWeb中的全局异常处理器是何时何地如何执行的。

在进行学习之前需要先知道:HTTP执行流程,SpringMVC执行流程


一、全局异常处理器的类型


全局异常处理器的父接口是 HandlerExceptionResolver,简单来说就是实现或间接实现它的类就叫全局异常处理器。

package org.springframework.web.servlet;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.lang.Nullable;public interface HandlerExceptionResolver {@NullableModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

HandlerExceptionResolver 的继承关系图
在这里插入图片描述


1-1、实现方式一


SpringBoot项目最大的特点就是注解,在SpringBoot项目中全局异常拦截的注解是@ControllerAdvice (@RestControllerAdvice = @ControllerAdvice + @ResponseBody)


使用 @ControllerAdvice的类最终会生成 ExceptionHandlerExceptionResolver


1-2、实现方式二


重写 doResolveHandlerMethodException 方法,然后注册当前的bean

public class ExceptionHandler extends AbstractHandlerMethodExceptionResolver {private final static Logger logger = LoggerFactory.getLogger(ExceptionHandler.class);@Overrideprotected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception ex) {return new ModelAndView();}
}

二、全局异常拦截点


2-1、入口


org.springframework.web.servlet.DispatcherServlet#doDispatch 这个方法就是SpringMVC的执行流程的核心代码了,下面是简化代码

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {// ...mv = ha.handle(processedRequest, response, mappedHandler.getHandler());// ....}catch (Exception ex) {dispatchException = ex;}catch (Throwable err) {dispatchException = new NestedServletException("Handler dispatch failed", err);}// 异常处理的入口processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}catch (Exception ex) {// ....}catch (Throwable err) {// ....}finally {// ...       }
}

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);// 异常处理mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}// ...
}

org.springframework.web.servlet.DispatcherServlet#processHandlerException

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,@Nullable Object handler, Exception ex) throws Exception {// Success and error responses may use different content typesrequest.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;if (this.handlerExceptionResolvers != null) {// 遍历循环所有的拦截器来尝试处理这个异常(拦截器已经按照 order 排好序了)for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);// 只有返回了 ModelAndView 才结束,不然一直往下走if (exMv != null) {break;}}}// ...// 如果没有全局异常处理器 可以处理这个异常 就继续抛出去throw ex;
}

2-2、全局异常拦截器是如何注入到 DispatcherServlet 的


上面看到是从 handlerExceptionResolvers 从获取所有的异常处理器,它是一个list

@Nullable
private List<HandlerExceptionResolver> handlerExceptionResolvers;

在DispatcherServlet里面有一个onRefresh方法,它是重写的父类FrameworkServlet的,在初始化ServletBean的时候会被调用一次,它里面会做很多初始化的操作,其中一个就是获取容器里面的全局异常拦截器


一层层看上去其实是 Servlet接口的 init方法触发的

@Override
protected void onRefresh(ApplicationContext context) {initStrategies(context);
}protected void initStrategies(ApplicationContext context) {// ...initHandlerExceptionResolvers(context);// ...
}

找到bean容器里面的所有异常拦截器,把它存在 handlerExceptionResolvers 里面,并排序


private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// 从bean容器里面找到所有的 HandlerExceptionResolverMap<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());// 排序AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}// ...
}

三、ControllerAdvice 如何解析、执行


3-1、解析


在springframework 中有这样一个类 ExceptionHandlerExceptionResolver

package org.springframework.web.servlet.mvc.method.annotation;public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {// ...
}

⚠️:可以回到【全局异常处理器的类型】的图看看,ExceptionHandlerExceptionResolver其实就是全局异常处理器HandlerExceptionResolver的子类


它实现了 InitializingBean,重写了afterPropertiesSet(这个方法会在bean初始化完之后执行)

@Override
public void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beansinitExceptionHandlerAdviceCache();// ...
}

initExceptionHandlerAdviceCache 会把所有使用了@ControllerAdvice 的bean找到并把它存在自己的参数里面

private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {return;}// 找到所有使用了 @ControllerAdvice 的beanList<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}// 解析全部的 ExceptionHandler 注解ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {// 存入当前的类参数里面this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}// ...
}

org.springframework.web.method.ControllerAdviceBean#findAnnotatedBeans

public static List<ControllerAdviceBean> findAnnotatedBeans(ApplicationContext context) {ListableBeanFactory beanFactory = context;if (context instanceof ConfigurableApplicationContext) {// Use internal BeanFactory for potential downcast to ConfigurableBeanFactory abovebeanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();}List<ControllerAdviceBean> adviceBeans = new ArrayList<>();// 遍历所有的beanfor (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) {if (!ScopedProxyUtils.isScopedTarget(name)) {// 找到符合的beanControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class);if (controllerAdvice != null) {// 存起来adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice));}}}// 排序OrderComparator.sort(adviceBeans);return adviceBeans;
}

配合@ControllerAdvice 注解的通常是 @ExceptionHandler 它用来制定具体的异常,把所有的 ExceptionHandler都存入了 mappedMethods 中org.springframework.web.method.annotation.ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver

public ExceptionHandlerMethodResolver(Class<?> handlerType) {for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {addExceptionMapping(exceptionType, method);}}
}
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {Method oldMethod = this.mappedMethods.put(exceptionType, method);if (oldMethod != null && !oldMethod.equals(method)) {throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +exceptionType + "]: {" + oldMethod + ", " + method + "}");}
}

至此@ControllerAdvice的解析完成

  1. 生成了一个ExceptionHandlerExceptionResolver,它通过多级实现了 HandlerExceptionResolver
  2. 所有使用@ControllerAdvice的类都存在了 exceptionHandlerAdviceCache 中
  3. 所有使用 @ExceptionHandler 的方法否存在了mappedMethods 中

3-2、执行


  1. 从【2-1】得知,执行异常处理器的时候是执行 HandlerExceptionResolver.resolveException方法(它只有这一个方法)
  2. 从【3-1】得知,所有使用 @ControllerAdvice 注解的类都被存在了ExceptionHandlerExceptionResolver 中
  3. 从【1】得知,ExceptionHandlerExceptionResolver的继承关系如下图
    在这里插入图片描述

一层层去看调用关系,最终会执行的是 (这个很简单直接去看即可)org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#doResolveHandlerMethodException

执行过程就是循环exceptionHandlerAdviceCache中的每一个全局拦截器,再循环每个拦截器里面的mappedMethods看哪个可以匹配上,就执行哪个


四、其它


4-1、设置HTTP状态码


大多数情况下我们会自定义返回值code,比如未鉴权,返回给前端HTTP状态码是200,code为401,但在某些情况下也会直接返回HTTP状态码401,可以使用 @ResponseStatus

@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(Exception.class)
public ResultObj bizExceptionHandler(Exception e) {log.info("全局异常拦截", e);return ResultObj.success();
}

4-2、异常处理器排序


springframework 里面提供了一个Ordered 接口,实现它重写里面 getOrder 方法就可以进行排序了


4-3、所谓全局异常

并不是系统任何异常都会被它所拦截,因为我们已经知道它的执行点是在MVC的流程中,所以就只有HTTP异常才会被拦截处理

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

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

相关文章

CorelDRAW2024新特性全解析!

CorelDRAW2024是一款备受赞誉的图形设计软件&#xff0c;它以其强大的功能和用户友好性赢得了全球数百万设计师的青睐。该软件提供了丰富的绘图、排版、图像处理、矢量编辑以及网页设计工具&#xff0c;无论是初学者还是专业设计师&#xff0c;都能在这款软件中找到满足自己需求…

精品录播|电磁场数值仿真技术及天线设计与应用

电磁场数值仿真技术及天线设计与应用

【技术分享】 OPC UA安全策略证书简述

那什么是OPC UA证书&#xff1f;用途是什么&#xff1f; 简单来说它是身份验证和权限识别。 OPC UA使用X.509证书标准&#xff0c;该标准定义了标准的公钥格式。建立UA会话的时候&#xff0c;客户端和服务器应用程序会协商一个安全通信通道。数字证书&#xff08;X.509&#x…

Android Compose四: 常用的组件 Text

Text Composable fun Text(text: String, //用于设置显示文本modifier: Modifier Modifier, //设置形状大小点击事件等color: Color Color.Unspecified, //fontSize: TextUnit TextUnit.Unspecified,fontStyle: FontStyle? null,fontWeight: FontW…

RockChip Android8.1 EthernetService分析

一:概述 本篇文章将围绕RK Android8.1 SDK对Ethernet做一次框架分析,包含Framework层和APP层。 当前版本SDK默认只支持一路Ethernet,熟悉Ethernet工作流程后通过修改最终会在系统Setting以太网中呈现多路选项(可以有多种实现方式),博主通过增加ListPreference实现的效果…

Linux 操作系统网络编程1

目录 1、网络编程 1.1 OSI 网络七层模型 1.1.1 OSI 参考模型 1.1.2 网络数据传输过程 2 传输层通信协议 2.1 TCP 2.1.1 TCP的3次握手过程 2.1.2 TCP四次挥手过程 2.2 UDP 3 网络编程的IP地址 4 端口 5 套接字 1、网络编程 1.1 OSI 网络七层模型 1.1.1 OSI 参考模型…

5月13号作业

使用消息队列实现的2个终端之间的互相聊天 并使用信号控制消息队列的读取方式&#xff1a; 当键盘按ctrlc的时候&#xff0c;切换消息读取方式&#xff0c;一般情况为读取指定编号的消息&#xff0c;按ctrlc之后&#xff0c;指定的编号不读取&#xff0c;读取其他所有编号的消息…

【江南大学×朗汀留学】部分留学录取案例合集

朗汀留学 X 江南大学 尽管客观条件如此艰难&#xff0c;朗汀留学的同学们还是斩获众多名校的录取。成绩属于过去&#xff0c;我们继续努力创造更好未来。 以下为我们摘取的江南大学部分学生案例供大家参考&#xff0c;再次恭喜所有获得理想大学offer的学生们&#xff0c;你们…

C# OpenCvSharp Demo - Mat格式化输出、Mat序列化和反序列化

C# OpenCvSharp Demo - Mat格式化输出、Mat序列化和反序列化 目录 效果 项目 代码 下载 效果 直接输出&#xff1a;Mat [ 3*2*CV_8UC3, IsContinuousTrue, IsSubmatrixFalse, Ptr0x1eb73ef9140, Data0x1eb73ef91c0 ]格式化输出&#xff1a;默认风格[ 91, 2, 79, 179, …

【计算机网络】http协议的原理与应用,以及https是如何保证安全传输的

HTTP 超文本传输协议&#xff08;英文&#xff1a;HyperText Transfer Protocol&#xff0c;缩写&#xff1a;HTTP&#xff09;是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。 HTTP的发展是由蒂姆伯纳斯-李于1989年在欧洲核子研究组织…

Web3Tools - 助记词生成

Web3Tools - 助记词生成工具 本文介绍了一个简单的助记词生成工具&#xff0c;使用 React 和 Material-UI 构建。用户可以选择助记词的语言和长度&#xff0c;然后生成随机的助记词并显示在页面上 功能介绍 选择语言和长度&#xff1a; 用户可以在下拉菜单中选择助记词的语言&…

Linux x86_64 dump_stack()函数基于FP栈回溯

文章目录 前言一、dump_stack函数使用二、dump_stack函数源码解析2.1 show_stack2.2 show_stack_log_lvl2.3 show_trace_log_lvl2.4 dump_trace2.5 print_context_stack 参考资料 前言 Linux x86_64 centos7 Linux&#xff1a;3.10.0 一、dump_stack函数使用 dump_stack函数…

大模型prompt实例:知识库信息质量校验模块

大模型相关目录 大模型&#xff0c;包括部署微调prompt/Agent应用开发、知识库增强、数据库增强、知识图谱增强、自然语言处理、多模态等大模型应用开发内容 从0起步&#xff0c;扬帆起航。 大模型应用向开发路径&#xff1a;AI代理工作流大模型应用开发实用开源项目汇总大模…

DRF 纯净版创建使用

【一】介绍 &#xff08;1&#xff09;使用原因 在Django中&#xff0c;contrib 包包含了许多内置的app和中间件&#xff0c;如auth、sessions、admin等&#xff0c;这些app在创建新的Django项目时默认是包含在内的。然而&#xff0c;在开发RESTful API时&#xff0c;可能不需…

linux性能监控之lsof

lsof&#xff1a;list open files&#xff0c;显示所有打开的文件以及进程信息&#xff0c;我们通常用来检查特定的文件被哪些进程打开 [rootk8s-master ~]# lsof --help lsof: illegal option character: - lsof: -e not followed by a file system path: "lp" lso…

网络传输,请每次都开启 TCP_NODELAY

原文&#xff1a;Marc Brooker - 2024.05.09 &#xff08;注&#xff1a;不必过于担心这个问题&#xff0c;大部分现代库&#xff0c;语言&#xff08;如 Go&#xff09;&#xff0c;代理&#xff08;如 Envoy&#xff09;&#xff0c;都默认设置了 TCP_NODELAY。如果遇到网络…

光耦 IS314W中文资料 IS314W引脚图及功能说明

IS314W是一款IGBT/MOSFET输出型光耦&#xff0c;由Isocom公司制造。它主要用于驱动用于电机控制和电源系统变频器的功率IGBT和MOSFET。以下是该产品的部分功能和参数&#xff1a; - 两个独立的光耦输出通道 - 轨对轨输出电压 - 最大峰值输出电流&#xff1a;1.0A - 最小峰值输…

DDoS攻击揭秘与网站防护策略

DDoS攻击&#xff08;分布式拒绝服务攻击&#xff09;是一种利用大量被控制的计算机或智能设备&#xff08;如僵尸网络&#xff09;对目标网站或服务器发起大量无效请求或数据流量&#xff0c;从而导致目标系统资源耗尽、服务崩溃或无法处理正常请求的攻击方式。这种攻击通常是…

【cpp】并发多线程 Unique

1. unique_lock 何时锁定资源。 unique_lock lock1 时候&#xff0c;还没有锁住资源。 实际是后面&#xff0c;显式的出发&#xff1a; 比如&#xff0c; lock.lock, 或 std::lock(lk1,lk2), 或者条件变量CV.wait(mtx, []{!re})。 #include <iostream> #include <mu…

《大数据分析-数据仓库项目实战》学习笔记

目录 基本概念 数据仓库 数据仓库整体技术架构 数据仓库主题 数据集市 数据仓库的血缘关系 数据仓库元数据管理 数据仓库的指标 数据仓库维度概念 HDFS Flume Hadoop Kafka 数据仓库分层模型 Superset 即席查询 Sqoop Atlas元数据管理 项目需求描述 系统目标…