解读Spring Boot框架中不同位置抛出异常的处理流程

news/2025/11/27 20:56:11/文章来源:https://www.cnblogs.com/nuccch/p/19279447

背景知识

对于Java Web开发而言,客户端发起的HTTP请求处理顺序为:Servlet容器 -> Filter -> Servlet -> Interceptor -> Controller,参考:Spring拦截器HandlerInterceptor与Filter方法执行顺序探究。
如下是一个客户端HTTP请求到达Serlvet容器后的完整执行流程:

-> org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun()                             --------> org.apache.coyote.AbstractProtocol$ConnectionHandler.process()                              |-> org.apache.coyote.AbstractProcessorLight.process()                                        |-> org.apache.coyote.http11.Http11Processor.service()                                      |-> org.apache.catalina.connector.CoyoteAdapter.service()                                 |-> org.apache.catalina.core.StandardEngineValve.invoke()                           Servlet容器-> org.apache.catalina.valves.ErrorReportValve.invoke()                              |-> org.apache.catalina.core.StandardHostValve.invoke()                             |-> org.apache.catalina.authenticator.AuthenticatorBase.invoke()                  |-> org.apache.catalina.core.StandardContextValve.invoke()                      |-> org.apache.catalina.core.StandardWrapperValve.invoke()                    |-> org.apache.catalina.core.ApplicationFilterChain.doFilter()              |-> org.apache.catalina.core.ApplicationFilterChain.internalDoFilter()  --------> 执行Filter.doFilter():如果在这里抛出了异常,在StandardWrapperValve.invoke()方法中会被捕获,并将异常对象保存到request属性中,返回到StandardHostValve.invoke()方法后检查request属性中的异常对象是否为空,如果不为空就执行RequestDispatcher.formard()访问/error路径;如果在Filter中未抛出异常,继续执行Servlet -> Interceptor -> Controller-> javax.servlet.http.HttpServlet.service()                                   -> org.springframework.web.servlet.FrameworkServlet.service()                  -> org.springframework.web.servlet.FrameworkServlet.doGet()等                 -> org.springframework.web.servlet.FrameworkServlet.processRequest()        -> org.springframework.web.servlet.DispatcherServlet.processRequest()  -> org.springframework.web.servlet.DispatcherServlet.doService()        -> org.springframework.web.servlet.DispatcherServlet.doDispatch()     -> org.springframework.web.servlet.HandlerExecutionChain.applyPreHandle()-> 执行HandlerInterceptor.preHandle()                                    -> org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle()-> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal()-> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod()-> org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle()-> org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest()-> org.springframework.web.method.support.InvocableHandlerMethod.doInvoke()-> 执行Controller方法-> org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue()-> org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue()-> org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters()-> org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyAdviceChain.beforeBodyWrite()-> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice.beforeBodyWrite()<-<-<--> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.getModelAndView()<-<-:如果在HandlerInterceptor.preHandle()方法或者Controller方法中抛出异常,会在DispatcherServlet.doDispatch()方法中被捕获,进入到DispatcherServlet.processDispatchResult()方法,最后调用全局异常拦截器进行处理-> org.springframework.web.servlet.DispatcherServlet.applyDefaultViewName()-> 执行HandlerInterceptor.postHandle()-> 执行HandlerInterceptor.afterCompletion()<-<-<-<-<-<-<-<-<-<-<-<-
<- 将响应数据写回客户端

在Filter中抛出异常

客户端发起的HTTP请求最先到达的组件就是Filter,如果在Filter.doFilter()方法中抛出了异常,在StandardWrapperValve.invoke()方法中会被捕获,并将异常对象保存到request属性中,返回到StandardHostValve.invoke()方法后检查request属性中的异常对象是否为空,如果不为空就执行RequestDispatcher.formard()访问/error路径。

// org.apache.catalina.core.StandardWrapperValveprivate void exception(Request request, Response response,Throwable exception) {// 将在Filter.doFilter()方法中抛出的异常对象保存到request属性中,属性名为RequestDispatcher.ERROR_EXCEPTION,即:"javax.servlet.error.exception"request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, exception);// 设置HTTP响应状态码response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);// 设置错误状态标志response.setError();
}
// org.apache.catalina.core.StandardHostValvepublic final void invoke(Request request, Response response)throws IOException, ServletException {// 省略其他代码...try {// 省略其他代码...// 从request属性中获取异常对象Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);// Protect against NPEs if the context was destroyed during a// long running request.if (!context.getState().isAvailable()) {return;}// Look for (and render if found) an application level error pageif (response.isErrorReportRequired()) {if (t != null) {// 如果异常不为空,throwable(request, response, t);} else {status(request, response);}}// 省略其他代码...} finally {// 省略其他代码...}
}protected void throwable(Request request, Response response,Throwable throwable) {// 省略其他代码...status(request, response);// 省略其他代码...
}// 在custom()方法中调用RequestDispatcher.forward()访问/error路径
private boolean custom(Request request, Response response,ErrorPage errorPage) {if (container.getLogger().isDebugEnabled()) {container.getLogger().debug("Processing " + errorPage);}try {// Forward control to the specified locationServletContext servletContext =request.getContext().getServletContext();RequestDispatcher rd =servletContext.getRequestDispatcher(errorPage.getLocation());if (rd == null) {container.getLogger().error(sm.getString("standardHostValue.customStatusFailed", errorPage.getLocation()));return false;}if (response.isCommitted()) {// Response is committed - including the error page is the// best we can dord.include(request.getRequest(), response.getResponse());} else {// Reset the response (keeping the real error code and message)response.resetBuffer(true);response.setContentLength(-1);// 执行RequestDispatcher.forward()方法访问错误页面(默认为/error)rd.forward(request.getRequest(), response.getResponse());// If we forward, the response is suspended againresponse.setSuspended(false);}// Indicate that we have successfully processed this custom pagereturn true;} catch (Throwable t) {ExceptionUtils.handleThrowable(t);// Report our failure to process this custom pagecontainer.getLogger().error("Exception Processing " + errorPage, t);return false;}
}

正因为如此,在Filter中抛出的异常无法被全局异常拦截器处理,如果希望在全局异常拦截器中处理Filter中抛出的异常,需要自定义org.springframework.boot.autoconfigure.web.ErrorController实现。

// 自定义org.springframework.boot.autoconfigure.web.ErrorController实现,将在Filter中抛出的异常交给全局异常拦截器处理@RequestMapping("/error")
public class DefaultErrorController implements ErrorController {@RequestMappingpublic void handleError(HttpServletRequest req) throws Throwable {Object exceptAttr = req.getAttribute("javax.servlet.error.exception");if (exceptAttr != null) {// 将异常信息抛给全局异常拦截器处理throw (Throwable) exceptAttr;}}
}

在HandlerInterceptor或Controller中抛出异常

如果在HandlerInterceptor.preHandle()方法或者Controller方法中抛出异常,会在DispatcherServlet.doDispatch()方法中被捕获,进入到DispatcherServlet.processDispatchResult()方法,最后调用全局异常拦截器进行处理。

// org.springframework.web.servlet.DispatcherServlet// 省略其他代码.../*** Handle the result of handler selection and handler invocation, which is* either a ModelAndView or an Exception to be resolved to a ModelAndView.*/
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,HandlerExecutionChain mappedHandler, ModelAndView mv, Exception exception) throws Exception {boolean errorView = false;// 如果异常对象不为空if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}// 省略其他代码...if (mappedHandler != null) {// 不论HandlerInterceptor.preHandle()方法或Controller方法是否抛出异常,HandlerInterceptor.afterCompletion()方法总是会被调用// 特别注意:如果HandlerInterceptor.preHandle()方法未抛出异常,而是明确返回了false,HandlerInterceptor.afterCompletion()方法不会被调用mappedHandler.triggerAfterCompletion(request, response, null);}
}protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,Object handler, Exception ex) throws Exception {// Check registered HandlerExceptionResolvers...ModelAndView exMv = null;// 在这里通过全局异常拦截器处理在Controller或HandlerInterceptor.preHandle()方法中抛出的异常for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}// 省略其他代码...
}

解读完毕。

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

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

相关文章

tips:LVGL 定时器触发周期不准确(实际间隔 设定间隔)问题排查与解决方案

问题现象 在使用 LVGL 库开发嵌入式 GUI 时,创建了一个设定为 1 秒(1000ms)触发一次的定时器,但实际观察到定时器回调函数的执行间隔明显超过 1 秒,导致依赖该定时器的功能(如时间显示更新)变得缓慢。 问题根源…

docker离线安装emqx(麒麟aarch64)

最近需要在麒麟系统安装emqx,由于emqx没有麒麟系统的安装包且源码编译依赖版本较难管理,因此采用docker容器化部署,现在回忆总结一下emqx的docker离线部署步骤。这里是麒麟系统离线安装docker和docker-compose的步骤…

云斗学院 NOIP 考前练手公益赛 Round 1 题目分析

应该比CSP-S还简单。 T1 分讨+贪心即可。 T2 简单题,贪心 T3 原题,做过,USACO的题目,大概是只吃 \([l,r]\) 的,然后留一个 \(k\) 最后给一个牛吃。题单里面有。 T4 好玩。 我们假设我们并没有看到数据随机。 题目…

第6章 基于应变的单轴疲劳分析 11

引言 局部应变-寿命法的核心假设是:缺口构件的裂纹形核与小裂纹扩展阶段寿命,与光滑实验室试样在相同循环变形条件(即裂纹起始部位的局部应变控制材料行为)下的寿命一致。如图6.1所示,基于这一概念,若已知试样局…

C++写有一个2D 小游戏(贪吃蛇)

用 C++ 开发游戏需要结合图形库、输入处理、游戏逻辑等模块,以下以2D 小游戏(贪吃蛇)为例,展示 C++ 游戏开发的核心流程,使用跨平台图形库SFML(简单易用,适合入门)。 一、准备工作 1. 安装 SFML 库下载地址:S…

NOIP day -2 笔记

马上NOIP了,写点笔记攒一下rp P2824[排序] 十分巧妙的数据结构题,关键点在于如何正确的处理排序的结果。 直接暴力做显然会被T飞,由此我们需要考虑用一种复杂度更低的方法去标记排序。 对于一个点 \(x\) 做包含 \(x…

专精

要在自己喜欢的方向去专精,专精的本质就是:通过极致的"慢" 和"专注", 在一个狭小的领域里,积攒出能瞬间破解,别人没有的穿透力 比如 哲学 : 哲学史那么大,全研究,可能就是个知道分子,但如…

对比说明Java NIO框架和传统的IO框架的优缺点

Java NIO 框架与传统 IO(BIO)框架的优缺点对比,核心围绕性能、易用性、适用场景展开,以下从双方视角分别分析: 一、传统 IO(BIO)框架的优缺点 优点:API 简单直观,开发成本低BIO 基于 “流” 模型设计(如Inpu…

CF2157C Meximum Array 2

限制分开讨论。 首先对于一个位置,如果两个地方的限制都有,那么填 \(k + 1\),因为此时不能填 \(< k\) 的数,也不能填 \(k\),因此填 \(k + 1\)。 如果什么限制都没有,那当然是填什么无所谓。 重要的就是只有两…

如何在实际项目中选择使用Java NIO框架还是传统IO框架?

在实际项目中选择 Java NIO 框架还是传统 IO(BIO)框架,核心是匹配项目的技术场景、性能需求、开发成本三者的平衡。以下从决策维度、场景分类、选型建议三个层面给出具体方案: 一、核心决策维度 选择前需明确以下关…

AT_fps_24_b 整数の組

生成函数是简单的,列出生成函数 然后对后两个的分母因式分解发现能和前两项消掉,最后是 \([x^n]\frac{1}{(x-1)^2}\)。 还有一种是枚举前两种,然后 \(d\) 跟 \(t=n-a-b\) 模 2 同余,且满足 \(3d\le t\) 所以看 \(\…

详细介绍:【数据结构初阶】单链表

详细介绍:【数据结构初阶】单链表pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco&…

第五十篇

今天是11月27号,上了体育和数据结构

每日随笔

今天背了第四单元的英语单词,看了一会《代码大全2》

2025年日语自学软件推荐:最适合零基础与进阶者的优质口碑选择

2025年日语自学软件推荐:最适合零基础与进阶者的优质口碑选择学习一门新语言时,合适的自学工具往往能让我们少走弯路。对于选择自学的日语爱好者来说,如何在众多日语自学软件中找到真正适合自己的那一款日语自学软件…

ABC386 VP总结

比赛链接 ResultE 题没开 LL 爽挂 3 发,F 题咋是压哨过的 Solution F - Operate K 令 \(dp_{i,j}\) 为 \(S\) 的前 \(i\) 位和 \(T\) 的前 \(j\) 为的最小编辑距离,转移是显然的。因为 \(dp_{i,j}\) 只会从 \(dp_{i,…

探究Spring Boot框架中访问不存在的接口时触发对error路径的访问

先说结论 默认情况下在Spring Boot框架中访问不存在的接口时会触发对"/error"路径的访问,这是由Spring Boot框架的默认错误处理机制导致的,核心是ErrorMvcAutoConfiguration自动配置类在起作用。 追根溯源…

tarjan 强连通分量、缩点、点双、割点、割边(桥)

有向图 强连通分量、缩点 取 cmin(low[u], dfn[v]) 时 v 一定要在栈里。 弹栈时要将 u 也弹出。 int dfn[N], low[N], dfnp, st[N], sp, vis[N], bl[N], blp; void tarjan(int u) {vis[st[++sp] = u] = 1;dfn[u] = low…

2025最新智慧停车与门禁系统解决方案推荐——骏通智能,专注出入口控制与智能化管理,车牌识别、道闸管理、门禁解决方案、通道闸、停车场服务、人脸门禁一站式解决

随着智慧城市建设的加速推进,智慧停车与智能门禁系统已成为现代建筑、社区及商业场所的标配设施。在2025年的出入口控制与智能化管理领域,骏通智能凭借多年技术沉淀与创新实力,为各类场景提供高效、安全、智能的解决…

我踩坑后总结:企业微信客服API接入客服系统,90%的人都搞错了!

vx:llike620 gofly.v1kf.com 最近在配置企业微信客服时,我在域名备案这个问题上踩了不少坑,结果发现大多数人的理解都存在误区。今天就把我的实战经验分享给大家,帮你少走弯路! 两个后台,两种不同的规则 首先必须…