Spring Boot项目的404是如何发生的

news2024/11/13 16:17:01

 问题

在日常开发中,假如我们访问一个Sping容器中并不存在的路径,通常会返回404的报错,具体原因是什么呢?

结论

 错误的访问会调用两次DispatcherServlet:第一次调用无法找到对应路径时,会给Response设置一个错误状态,第二次是根据这个状态执行预先设置了error属性的DispatcherServlet。而正确的访问只会调用一次DispatcherServlet。

原理

我们知道,基于SpringMvc原理的Spring Boot项目,所有的路由请求默认都是由DispatcherServlet类来负责处理的?

如果开启断点调试会发现在第二次进入DispatcherServlet的doDispatch方法时,便直接返回了404错误:

那么问题的重点应该出现在两次DispatcherServlet的调用上,通过调试可以发现,最后一次的调用分析的意义不大,因为从它的request属性就能看出来,它预先设置了一堆的错误属性,明显就是为了返回错误,走了一遍DispatcherServlet的标准流程。

重点只有两点:

第一次执行DispatcherServlet的过程中发生了什么?

错误的请求为什么会有两次DispatcherServlet调用?

1.1 第一次执行DispatcherServlet

通过断点调试,可以看到在执行DispatcherServlet中的doDispatch方法的时候进入了HttpRequestHandlerAdapter的handle方法

public class HttpRequestHandlerAdapter implements HandlerAdapter {
    public HttpRequestHandlerAdapter() {
    }
    public boolean supports(Object handler) {
        return handler instanceof HttpRequestHandler;
    }
    @Nullable
    public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        ((HttpRequestHandler)handler).handleRequest(request, response);
        return null;
    }
......

接着又执行了ResourceHttpRequestHandler的handleRequest方法,在执行的过程中,在容器中没有找到对应的路径,所以对response设置了404错误:

也就是

response.sendError(404)

它的底层实际上是给Response类的一个私有属性errorState,设置了错误状态:

public boolean setError() {
        return this.errorState.compareAndSet(0, 1);
    }

 这样设置有什么用呢?

这就涉及到第二次执行DispatcherServlet的原因了。

1.2 错误请求为什么会执行两次DispatcherServlet

重点在StandardHostValve类的invoke方法中:

public final void invoke(Request request, Response response) throws IOException, ServletException {
        ......
                try {
                    //第一次执行DispatcherServlet的原因
                    if (!response.isErrorReportRequired()) {
                        //执行正常的请求
                        context.getPipeline().getFirst().invoke(request, response);
                    }
                } catch (Throwable var10) {
                    ExceptionUtils.handleThrowable(var10);
                    this.container.getLogger().error("Exception Processing " + request.getRequestURI(), var10);
                    if (!response.isErrorReportRequired()) {
                        request.setAttribute("javax.servlet.error.exception", var10);
                        this.throwable(request, response, var10);
                    }
                }

                response.setSuspended(false);
                Throwable t = (Throwable)request.getAttribute("javax.servlet.error.exception");
                if (context.getState().isAvailable()) {
                    //第二次执行DispatcherServlet的原因
                    if (response.isErrorReportRequired()) {
                        AtomicBoolean result = new AtomicBoolean(false);
                        response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
                        if (result.get()) {
                            if (t != null) {
                                this.throwable(request, response, t);
                            } else {
                                //执行错误请求
                                this.status(request, response);
                            }
                        }
                    }
                    ......

两次执行的原因是因为

response.isErrorReportRequired() 

public class Response implements HttpServletResponse {
public boolean isErrorReportRequired() {
     return this.getCoyoteResponse().isErrorReportRequired();
 }
......

public final class Response {

public boolean isErrorReportRequired() {
    return this.errorState.get() == 1;
}
.....

 也就是根据Response类的私有属性errorState来判断的。

第一次执行DispatcherServlet的时候,由于errorState的值还是初始化值0,所以可以正常执行,执行的时候找不到对应的路径资源,便执行了response.sendError(404)方法,给errorState赋值为1。

这是StandardHostValve类中,invoke执行的

 context.getPipeline().getFirst().invoke(request, response);

给errorState赋值1以后,又执行了:

this.status(request, response);

 它的执行链路是这样:

 status -> custom -> forward -> doForward -> processRequest ->doFilter。

也就是对本次请求执行了一次转发,最后重新调用了一遍

filterChain.doFilter(request, response)

的流程,走了错误调用。

在processRequest方法可以比较看的比较明确:

private void processRequest(ServletRequest request, ServletResponse response, State state) throws IOException, ServletException {
        DispatcherType disInt = (DispatcherType)request.getAttribute("org.apache.catalina.core.DISPATCHER_TYPE");
        if (disInt != null) {
            boolean doInvoke = true;
            if (this.context.getFireRequestListenersOnForwards() && !this.context.fireRequestInitEvent(request)) {
                doInvoke = false;
            }

            if (doInvoke) {
                if (disInt != DispatcherType.ERROR) {
                    state.outerRequest.setAttribute("org.apache.catalina.core.DISPATCHER_REQUEST_PATH", this.getCombinedPath());
                    state.outerRequest.setAttribute("org.apache.catalina.core.DISPATCHER_TYPE", DispatcherType.FORWARD);
                    this.invoke(state.outerRequest, response, state);
                } else {
                    this.invoke(state.outerRequest, response, state);
                }

                if (this.context.getFireRequestListenersOnForwards()) {
                    this.context.fireRequestDestroyEvent(request);
......

 等到DispatcherServlet再执行完一次,便能在浏览器看到404报错了。

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

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

相关文章

【React笔记初学总结一】React新手的学习流程笔记总结,掰开了揉碎了,下载安装基础结构学习

REACT学习记录 一、React是什么:二、尝试安装下载:三、理解都有什么四、基础网页学习:1.几个比较重要的资源包例子2.第一个react示例:(掰开了揉碎了,咱们先看懂它最简单的结构)3.第二个react示例…

Vue--Router(路由)

目录 一 Router(路由) 1.作用 2.实现步骤 3.注意 一 Router(路由) 1.作用 Router又叫做路由,简单来说,就是用来实现vue的页面之间跳转的。 我们都知道,使用vue必然会涉及到很多个组件,也就是页面,而页面之间肯定需…

mars3d实现GraphicLayer获取当前相机视角内的可视点位(矢量数据

效果: mars3d实现GraphicLayer获取当前相机视角内的可视点位(矢量数据 相关依赖api: 1. map.getExtent(); 提取地球当前视域边界,示例:{ xmin: 70, xmax: 140, ymin: 0, ymax: 55, height: 0, } 2.graphicLayer.eachGraphic遍…

Android12 MultiMedia框架之NuPlayer Surface

在学习NuPlayer创建Decoder和Renderer之前,得先看下video数据最终生产到什么地方去?答案是Surface,而且Surface是NuPlayer创建Decoder的条件。 APP会创建一层SurfaceView来显示视频层,同时内部会创建一个BLASTBufferQueue&#x…

睡前故事—绿色科技的未来:可持续发展的梦幻故事

欢迎来到《Bedtime Stories Time》。这是一个我们倾听、放松、并逐渐入睡的播客。感谢你收听并支持我们,希望你能将这个播客作为你睡前例行活动的一部分。今晚我们将讲述绿色科技的未来:可持续发展的梦幻故事的故事。一个宁静的夜晚,希望你现…

自适应简约大气科技数码产品公司网站源码系统 模版一键搭建 可自定义带源代码包以及搭建部署教程

系统概述 在当今这个数字化、信息化的时代,科技数码产品行业正处于高速发展的黄金时期。为了在这个竞争激烈的市场中脱颖而出,科技数码产品公司不仅需要拥有卓越的产品和技术,还需要一个能够完美展现其品牌形象和产品特色的网站。为此&#…

【PyTorch快速入门教程】02 Jupyter notebook安装及配置

文章目录 1 安装 Jupyter notebook2 安装 ipykernel3 更改 jupyter 默认配置3.1 生成配置文件3.2 关键配置信息 4 扩展插件推荐参考 1 安装 Jupyter notebook 一行命令搞定 python -m pip install jupyter 现在就可以打开Jupyter notebook来运行python啦。 jupyter notebook…

印尼语翻译通:AI驱动的智能翻译与语言学习助手

在这个多元文化交织的世界中,语言是连接我们的桥梁。印尼语翻译通,一款专为打破语言障碍而生的智能翻译软件,让您与印尼语的世界轻松接轨。无论是商务出差、学术研究,还是探索印尼丰富的文化遗产,印尼语翻译通都是您的…

自动驾驶系列—智能巡航辅助功能中的车道中央保持功能介绍

文章目录 1. 背景介绍2. 功能定义3. 功能原理4. 传感器架构5. 实际应用案例5.1 典型场景1:直道内行驶5.1.1 直道内居中行驶5.1.2 直道内跟车行驶 5.2 典型场景2:弯道内行驶5.2.1 弯道内居中行驶5.2.2 弯道内跟车行驶 5.3 典型场景3:道路边缘5…

【Godot4.2】MLTag类:HTML、XML通用标签类

概述 HTML和XML采用类似的标签形式。 之前在Godot中以函数库形式实现了网页标签和内容生成。能用,但是缺点也很明显。函数之间没有从属关系,但是多有依赖,而且没有划分出各种对象和类型。 如果以完全的面向对象形式来设计标签类或者元素类…

【Android】常见控件及使用

视图的设置 控件与布局都继承于视图,因此视图的设置方式对其作用都是一样的,接下来就先讲述视图的不同设置方式 设置视图的宽高: 对视图的宽高进行设置,首先确保XML中的宽高属性值为wrap_content,接着打开该页面对应…

MySQL第七次作业

Product表内容 字段名 字段描述 数据类型 主键 外键 非空 唯一 自增 Id 产品编号 Int(10) 是 否 是 是 否 Name 产品功能 Varchar(20) 否 否 是 否 否 Function 主要功能 Varchar(50) 否 否 否 否 否 Company 生产厂家 Varchar(20) 否 否 是 否 否 Address 家庭住址 Varchar(20…

VS2022配置Qt环境

文章目录 前言VS2022写Qt的好处下载插件前提条件离线下载在线安装配置VS For Qt 创建项目总结 前言 在许多开发环境中,Visual Studio 2022(VS2022)和Qt都是非常重要的工具。VS2022是微软开发的一款强大的集成开发环境(IDE&#x…

学习面向对象笔记

1. 面向对象三大特性 特性说明例子接口作为方法的标识,只要调用即可,不需要关心接口后是怎样实现的最常见的USB接口,只要插上对应的鼠标、键盘就可以使用,因为他们都是统一使用这个USB接口的继承子类继承父类的功能,子…

React工程化笔记

脚手架可以帮助我们快速的搭建一个项目结构,在我们之前学习 webpack 的过程中,每次都需要配置 webpack.config.js 文件,用于配置我们项目的相关 loader 、plugin,这些操作比较复杂,但是它的重复性很高,而且…

Spring Cloud Eureka快读入门Demo

1.什么是Eureka? Eureka 由 Netflix 开发,是一种基于REST(Representational State Transfer)的服务,用于定位服务(服务注册与发现),以实现中间层服务的负载均衡和故障转移&#xff…

弹性网络回归(Elastic Net Regression)

弹性网络回归(Elastic Net Regression)的详细理论知识推导 理论背景 弹性网络回归结合了岭回归(Ridge Regression)和Lasso回归(Lasso Regression)的优点,通过引入两个正则化参数来实现特征选择…

vue3+TS从0到1手撸后台管理系统

1.路由配置 1.1路由组件的雏形 src\views\home\index.vue(以home组件为例) 1.2路由配置 1.2.1路由index文件 src\router\index.ts //通过vue-router插件实现模板路由配置 import { createRouter, createWebHashHistory } from vue-router import …

解决jupyter argparse报错

jupyter argparse报错 文章目录 一、jupyter argparse报错 一、jupyter argparse报错 args parser.parse_args()这行代码改为: args parser.parse_args(args[])完整的代码为: import argparseparser argparse.ArgumentParser() parser.add_argumen…

【Dison夏令营 Day 21】用Python编写绘图

绘画 - 在屏幕上绘制线条和形状。单击标记形状的起点,再次单击标记形状的终点。可使用键盘选择不同的形状和颜色。 """Paint, for drawing shapes.Exercises1. Add a color. 2. Complete circle. 3. Complete rectangle. 4. Complete triangle. 5. A…