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

news/2025/11/27 20:39:46/文章来源:https://www.cnblogs.com/nuccch/p/19279370

先说结论

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

追根溯源

如下以Spring Boot 2.6.13版本源码进行解读,更多Spring Boot版本详见:spring-boot/docs/及spring-boot#support。

访问接口不存在时是否会执行到Interceptor

我们知道,客户端发起的HTTP请求处理顺序为:Servlet容器 -> Filter -> Servlet -> Interceptor -> Controller(参考:Spring拦截器HandlerInterceptor与Filter方法执行顺序探究),因此,当访问一个不存在的接口时,并不会真正到达Controller层。但是是否会执行到Interceptor,跟工程的配置参数有关。具体到实现来说,是根据DispatcherServlet.getHandler()方法返回值决定的。

// org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {// 省略代码...// Determine handler for the current request.mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {// 当getHandler方法返回值为空时,会执行noHandlerFound方法,返回HTTP状态码为404noHandlerFound(processedRequest, response);return;}// 省略代码...if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}// 省略代码...
}/*** No handler found → set appropriate HTTP response status.* @param request current HTTP request* @param response current HTTP response* @throws Exception if preparing the response failed*/
protected void noHandlerFound(HttpServletRequest request, HttpServletResponse response) throws Exception {if (pageNotFoundLogger.isWarnEnabled()) {pageNotFoundLogger.warn("No mapping for " + request.getMethod() + " " + getRequestUri(request));}if (this.throwExceptionIfNoHandlerFound) {throw new NoHandlerFoundException(request.getMethod(), getRequestUri(request),new ServletServerHttpRequest(request).getHeaders());}else {// 响应给客户端的HTTP状态码为404response.sendError(HttpServletResponse.SC_NOT_FOUND);}
}

实际上,DispatcherServlet.getHandler()方法返回值跟Spring Boot工程的配置参数有关系:
1.默认情况下,参数spring.mvc.static-path-pattern值为/**,此时DispatcherServlet.getHandler()方法返回值为org.springframework.web.servlet.HandlerExecutionChain实例对象(其中的handler成员变量为org.springframework.web.servlet.resource.ResourceHttpRequestHandler实例对象,interceptorList成员变量即为Interceptor列表),不为null,就会继续执行HandlerExecutionChain.applyPreHandle()方法,这样客户端请求就会执行到Interceptor层,如下:

//  org.springframework.web.servlet.HandlerExecutionChain
/*** Apply preHandle methods of registered interceptors.* @return {@code true} if the execution chain should proceed with the* next interceptor or the handler itself. Else, DispatcherServlet assumes* that this interceptor has already dealt with the response itself.*/
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {for (int i = 0; i < this.interceptorList.size(); i++) {HandlerInterceptor interceptor = this.interceptorList.get(i);// 执行各个Interceptor拦截器实例中的preHandle()方法if (!interceptor.preHandle(request, response, this.handler)) {triggerAfterCompletion(request, response, null);return false;}this.interceptorIndex = i;}return true;
}

2.如果在项目工程中明确指定为参数spring.mvc.static-path-pattern指定了其他值,比如:/static/**,此时如果请求一个不存在的接口(如:/api/hello),由于请求接口路径跟参数spring.mvc.static-path-pattern值不匹配,所以DispatcherServlet.getHandler()返回值为null,就无法执行到Interceptor层了。

剖析接口不存在时完整请求流程

如下通过源码解读的方式梳理当客户端发起一个不存在的接口请求时,是如何触发/error路径访问的。

我们已经知道,客户端请求首先到达的是Servlet容器,所以如下源码解析均基于Tomcat 9.0.68阐述(更多Tomcat版本详见:Apache Archive Distribution Directory)。

具体来说是以org.apache.catalina.core.StandardHostValve作为Tomcat容器的执行入口,完整的请求执行路径为:
-> org.apache.catalina.core.StandardHostValve.invoke()
-> org.apache.catalina.authenticator.AuthenticatorBase.invoke()
-> org.apache.catalina.core.StandardContextValve.invoke()
-> org.apache.catalina.core.StandardWrapperValve.invoke()
-> Filter列表
-> javax.servlet.http.HttpServlet.service()
-> org.springframework.web.servlet.FrameworkServlet.doGet()
-> org.springframework.web.servlet.FrameworkServlet.processRequest()
-> org.springframework.web.servlet.DispatcherServlet.doService()
-> org.springframework.web.servlet.DispatcherServlet.doDispatch()

1.在org.springframework.web.servlet.DispatcherServlet.doDispatch()方法中有2地方会检查到路径是否存在,其一:getHandler()方法返回值为null表示请求路径不存在,其二:在org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle()方法中会真正调用org.springframework.web.servlet.resource.ResourceHttpRequestHandler.handleRequest()方法,进而通过org.springframework.web.servlet.resource.ResourceHttpRequestHandler.getResource()方法判断请求路径是否真正存在。
2.如果请求路径不存在,就会设置响应状态码为404,具体是调用org.apache.catalina.connector.Response.sendError()方法设置HTTP响应状态码,响应消息以及其他状态值,供后续在org.apache.catalina.core.StandardHostValve.invoke()方法中使用

回到org.apache.catalina.core.StandardHostValve.invoke()方法中继续执行。

// org.apache.catalina.core.StandardHostValve.invoke()// 省略其他代码...// Look for (and render if found) an application level error page
if (response.isErrorReportRequired()) { // 这个判断条件就是在org.springframework.web.servlet.DispatcherServlet.doDispatch()方法中设置的// If an error has occurred that prevents further I/O, don't waste time// producing an error report that will never be readAtomicBoolean result = new AtomicBoolean(false);response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);if (result.get()) {if (t != null) {throwable(request, response, t);} else {status(request, response); // 最终会执行到这里}}
}// 省略其他代码...

org.apache.catalina.core.StandardHostValve.status()方法中会根据响应状态码查找对应的错误页面,最终调用org.apache.catalina.core.ApplicationDispatcher.forward()方法将请求跳转到错误页面路径(默认为/error)。

// org.apache.catalina.core.StandardHostValve.status()// 省略其他代码...// 根据在org.springframework.web.servlet.DispatcherServlet.doDispatch()方法中设置的错误响应码查找对于的错误页面对象
ErrorPage errorPage = context.findErrorPage(statusCode);
if (errorPage == null) {// Look for a default error pageerrorPage = context.findErrorPage(0);
}// 省略其他代码...// 调用custom()方法请求错误页面路径
if (custom(request, response, errorPage)) {response.setErrorReported();try {response.finishResponse();} catch (ClientAbortException e) {// Ignore} catch (IOException e) {container.getLogger().warn("Exception Processing " + errorPage, e);}
}
// org.apache.catalina.core.StandardHostValve.custom()// 省略其他代码...RequestDispatcher rd = servletContext.getRequestDispatcher(errorPage.getLocation());// 省略其他代码...rd.forward(request.getRequest(), response.getResponse());// 省略其他代码...

回顾上述执行流程可知,当客户端请求一个不存在的接口时会触发对/error路径的访问,这个过程是由Spring Boot框架跟Servlet容器一起配合实现的。

实际上通过对Spring Boot框架源码进行跟踪调试后发现,不仅仅是访问不存在的接口时会触发对/error路径的访问,当在Filter.doFilter()方法中抛出异常时,也会触发对/error路径的访问。

另外也确认了一个事实:可以为不同的HTTP错误响应状态码设置对应的页面。

那么,为什么在Spring Boot框架中触发访问的路径是/error,而不是其他路径呢?

为什么请求到/error路径

这要从Spring Boot框架的自动配置类org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration说起,本质上是通过它设定了默认的错误访问路径。

// org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration// 省略其他代码...@Bean
public ErrorPageCustomizer errorPageCustomizer(DispatcherServletPath dispatcherServletPath) {return new ErrorPageCustomizer(this.serverProperties, dispatcherServletPath);
}// 省略其他代码...

通过org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration自动配置类注入了组件org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer,该组件中的方法registerErrorPages()会在Spring Boot框架启动时调用,从而设定了默认错误访问路径为/error(即org.springframework.boot.autoconfigure.web.ErrorProperties类中的path变量,默认值为/error)。

实际上,这个默认的错误访问路径/error是可以通过参数server.error.path指定的。

server:error:path: /error

如下通过源码解析的方式阐述错误访问路径(默认为/error)是如何在Spring Boot框架启动时设置的。
-> org.springframework.context.support.AbstractApplicationContext.refresh()
-> org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh()
-> org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer()
-> org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getWebServerFactory()
-> org.springframework.boot.web.server.ErrorPageRegistrarBeanPostProcessor.postProcessBeforeInitialization()
-> org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration.ErrorPageCustomizer.registerErrorPages()

在Spring Boot框架中对应参数server.error.path指定URL路径的Controller为org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController,它实现了接口:org.springframework.boot.web.servlet.error.ErrorController

// org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController@Controller
@RequestMapping("${server.error.path:${error.path:/error}}")
public class BasicErrorController extends AbstractErrorController {// 省略其他代码...@RequestMapping(produces = "text/html")public ModelAndView errorHtml(HttpServletRequest request,HttpServletResponse response) {HttpStatus status = getStatus(request);Map<String, Object> model = Collections.unmodifiableMap(getErrorAttributes(request, isIncludeStackTrace(request, MediaType.TEXT_HTML)));response.setStatus(status.value());ModelAndView modelAndView = resolveErrorView(request, response, status, model);return (modelAndView == null ? new ModelAndView("error", model) : modelAndView);}@RequestMapping@ResponseBodypublic ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {HttpStatus status = getStatus(request);if (status == HttpStatus.NO_CONTENT) {return new ResponseEntity<>(status);}Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));return new ResponseEntity<>(body, status);}// 省略其他代码...
}

另外,org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController也是通过自动配置类org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration注入到Spring容器中的。

// org.springframework.boot.autoconfigure.web.servlet.error.ErrorMvcAutoConfiguration// 省略其他代码...@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,ObjectProvider<ErrorViewResolver> errorViewResolvers) {return new BasicErrorController(errorAttributes, this.serverProperties.getError(),errorViewResolvers.orderedStream().collect(Collectors.toList()));
}// 省略其他代码...

在项目开发中,还可以根据需要自定义新的org.springframework.boot.web.servlet.error.ErrorController实现。例如:在Filter中抛出异常时也会触发对/error路径的访问,但是在Filter中抛出的异常信息无法被全局异常拦截器捕获,此时可以在自定义org.springframework.boot.web.servlet.error.ErrorController中把在异常信息抛出来,统一实现异常在全局异常拦截器(使用@ControllerAdvice注解标记)中进行处理。

@RequestMapping("/")
public class DefaultErrorController implements ErrorController {@RequestMapping("/error")public void handleError(HttpServletRequest req) throws Throwable {Object exceptAttr = req.getAttribute("javax.servlet.error.exception");if (exceptAttr != null) {// 将异常抛出去统一在全局异常拦截器中处理throw (Throwable) exceptAttr;}}
}

关于/error路径的总结

在Spring Boot框架中,关于/error路径总结如下:
1.可以通过参数server.error.pathorg.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController指定访问路径,默认值为:/error
2.当出现如下两种场景时都会触发对/error路径的访问:

  • 访问不存在的接口,同时HTTP响应状态码为404
  • 在Filter中抛出异常时

至此,关于在Spring Boot框架中触发访问/error路径的原因和执行流程分析完毕。

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

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

相关文章

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

香橙派上进行MQTT数据存储客户端开发(一)基本环境配置

香橙派上进行MQTT数据存储客户端开发(一)基本环境配置基本信息 云服务器配置:EMQX Cloud 类型为 Serverless 主机:Orangepi5max 16G (ARM64 架构) + 32G tf卡 系统:Orangepi5max_1.0.0_ubuntu_jammy_desktop_xf…

GEO 优化价格大比拼,哪家最便宜?三大高性价比机构推荐

GEO 优化已成为企业拓展本地市场、精准触达目标客户的核心手段。无论是线下门店引流、区域品牌推广,还是本地服务曝光,优质的 GEO 优化都能让企业在搜索引擎、地图应用等平台占据更靠前的位置,从而获取更多潜在客户…

2025年AI学习机哪个品牌好?热门品牌功能与效果全解析

2025年AI学习机哪个品牌好?热门品牌功能与效果全解析一、推荐背景与评价体系 最新教育硬件调研数据显示,2025年家长在选择AI学习机时最关注的三大痛点为:“学习内容是否精准适配”、“AI技术是否成熟可靠”、“学习…

2025年知名的长租公寓有哪些:权威榜单与精选解析

2025年知名的长租公寓有哪些:权威榜单与精选解析在城市青年的居住选择谱系中,焦虑正从零散的个体困扰演化为具有普遍性的时代命题。面对海量租房选项与参差的服务现实,许多人不得不在品牌公寓、中介平台与个人房源间…

编程中的枚举法与数学上的穷举法有何区别?

枚举法和穷举法在核心思想上都强调“逐一尝试所有可能性”,但在编程和数学中的侧重点、应用场景及实现方式存在显著差异。以下是具体对比及示例说明: 1. 核心区别维度 枚举法(编程) 穷举法(数学)目的 通过遍历所…

如百钱百鸡问题,枚举法和穷举法有何不同

百钱百鸡问题是一个经典的数学谜题,要求用100文钱买100只鸡,其中公鸡5文钱一只,母鸡3文钱一只,小鸡1文钱三只。求解公鸡、母鸡、小鸡的数量组合。枚举法和穷举法在解决该问题时,核心思路都是遍历所有可能的组合,…

2025年长租公寓排名:最新专业榜单与推荐

2025年长租公寓排名:最新专业榜单与推荐在城市青年的居住版图中,租房早已不只是找个落脚处,而是一次关乎生活品质与精神归属的系统选择。面对品牌公寓、中介平台与个人房源的多重岔路,许多人陷入“租房迷茫”——耗…

从零开始建网站在线客服系统:域名+服务器,到底怎么选才不踩坑?

🌐 从零开始建站:域名+服务器,到底怎么选才不踩坑? 你是不是也听过这种说法:“建网站嘛,域名和服务器肯定少不了!”——没错,这确实是铁律。但作为一个刚折腾完个人博客的过来人,我想告诉你:真正关键的,不…

根本魔法语言数组 (一) (C语言)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年租房品牌排名:TOP10权威揭秘与必读

2025年租房品牌排名:TOP10权威揭秘与必读在租房市场中,租客们常常陷入“租房迷茫”与“体验落差”的困境。他们在“品牌公寓”“中介租房”“个人房源”等众多选择中徘徊,租房过程繁琐,不仅耗费大量时间和精力,还…

Spring Cloud工程中使用Nacos配置中心的2种方式

先说结论 使用Nacos作为配置中心时,因工程配置文件名称的不同,配置Nacos参数的方式也有所不同。 如下示例使用的框架及服务版本信息为:Spring Boot:2.6.13 Spring Cloud:2021.0.5 Spring Cloud Alibaba:2021.0.5…

《程序员修炼之道:从小工到专家》阅读笔记5

1.纯文本的威力:坚持使用纯文本(如JSON, YAML,源代码)来存储知识。 好处:可读性、可移植性、易于被各种工具(如grep, awk, sed)处理。它是知识的“持久存储”最佳格式。 2.Shell的力量:不要轻视命令行。它是你的…

那为什么go 就能用同步的写法,而且不用协程的情况下,实现异步编程,而且还不阻塞os线程

这是一个非常棒的问题!你提到的 Go 语言 能用“同步写法”实现高性能异步 I/O,而且不阻塞 OS 线程,这背后是 Go 运行时(runtime)精心设计的 M:N 调度模型 + 非阻塞 I/O 封装 的结果。我们来一步步拆解为什么 Go 能…

人工智能之数据分析 Matplotlib:第三章 基本属性

人工智能之数据分析 Matplotlib:第三章 基本属性人工智能之数据分析 Matplotlib 第三章 基本属性@目录人工智能之数据分析 Matplotlib前言一. 绘图标记二. 绘图线三. 轴标签和标题四. 网格线五. 绘制多图使用 plt.s…

卡内基梅隆大学五位研究生获科研奖学金

某中心与卡内基梅隆大学合作设立研究生科研奖学金项目,支持五位研究生在机器学习、自然语言处理、计算机视觉等领域开展前沿研究,涵盖算法偏见审计、泛化能力评估等关键技术方向。五位卡内基梅隆大学学生荣获某中心研…

URL地址转base64

URL地址转base64/** * 请求图片地址,返回结果进行base64编码 * @param imgUrl * @return */public String requestUrlToBase64(String imgUrl){ String result = null; HttpURLConnection connection = null; …

2025年租房去哪里找房源:独家榜单与深度解析

2025年租房去哪里找房源:独家榜单与深度解析在城市青年的居住议题中,“2025年租房去哪里找房源”已从日常决策演变为带有普遍焦虑的时代拷问。不少租客疲于在品牌公寓、中介平台与个人房源之间反复权衡,既担心流程冗…