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

 问题

在日常开发中,假如我们访问一个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报错了。

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

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

相关文章

uniapp页面跳转传参和动态修改NavigationBarTitle

一、需求 比如聊天界面,需要在上方展示对方的名字,我们这样需要动态数值的,就不能写在pages配置项里面。 二、实现 我们并没有在pages里面配置固定的title名,我们需要动态传到talk,然后动态修改绑定到这个title。好在u…

从零开始学量化~Ptrade使用教程(六)——盘后定价交易、港股通与债券通用质押式回购

盘后固定价交易 实现科创板、创业板的盘后固定价交易,界面如下显示: 交易 输入科创板或创业板代码,选择委托方向,输入委托价格、委托数量,点击“买入”或“卖出”按钮进行委托。可出现一个委托提示框提示是否继续委托操…

HCNA VRP基础

交换机可以隔离冲突域,路由器可以隔离广播域,这两种设备在企业网络中应用越来越广泛。随着越来越多的终端接入到网络中,网络设备的负担也越来越重,这时网络设备可以通过专有的VRP系统来提升运行效率。通过路由平台VRP是华为公司数…

怎么录屏?Windows和Mac电脑都适用的3种方法

在数字化时代的浪潮中,电脑录屏已经成为一种必备技能。无论是为了制作教学视频,记录游戏的高光时刻、还是为了保存下欢乐时光,录屏功能都在我们当中发挥着重要的作用。但是怎么录屏也成为一个难题,有时候用的电脑不一样&#xff0…

基于搜索二叉树的停车收费管理系统

系统效果&#xff1a;录入汽车信息 查看汽车信息 收费信息查看 查询车库车辆 代码展示&#xff1a; //SearchBinaryTree.h #pragma once #include<iostream> #include<string> #include<time.h> #include<Windows.h> using namespace std;template<…

ArkTS学习笔记_UI界面的状态管理简述

ArkTS学习笔记_UI界面的状态管理简述 背景&#xff1a; 我们在UI开发中&#xff0c;绝大多数的UI界面都是动态的、有用户交互的&#xff0c;为了实现动态交互&#xff0c;引入了一个概念“状态”&#xff0c;它主要是用来记录管理UI界面的状态变化&#xff08;数据变化&#x…

08 模型演化根本 深度学习推荐算法的五大范式

易经》“九三&#xff1a;君于终日乾乾&#xff1b;夕惕若&#xff0c;厉无咎”。九三是指阳爻在卦中处于第三位&#xff0c;已经到达中位&#xff0c;惕龙指这个阶段逐渐理性&#xff0c;德才已经显现&#xff0c;会引人注目&#xff1b;但要反思自己的不足&#xff0c;努力不…

ubuntu上模拟串口通信

前言 有时候写了一些串口相关的程序&#xff0c;需要调试的时候&#xff0c;又没有硬件&#xff0c;或者需要等其他模块完成才能一起联调。这样搭建环境费时费力&#xff0c;很多问题等到最后联调才发现就已经很晚了。 本文提供一种在ubuntu环境下模拟串口&#xff0c;直接就可…

django报错(一):python manage.py makemigrations,显示“No changes detected”

执行python manage.py makemigrations命令无任何文件生成&#xff0c;结果显示“No changes detected”。 解决方案一&#xff1a; 1、执行命令&#xff1a;python manage.py makemigrations –empty appname 2、删除其中的0001_initial.py文件&#xff08;因为这个文件内容是…

vue2+antd实现表格合并;excel效果

效果图 一、html <template><div><a-table :columns"columns" :dataSource"dataSource" rowKey"id" :pagination"false" bordered><template slot"content1" slot-scope"text">{{text}}…

单臂路由组网实验,单臂路由的定义、适用情况、作用

一、定义 单臂路由是指通过在路由器的一个接口上配置许多子接口,从而实现原来相互隔离的不同VLAN之间的互通。 子接口:把路由器上的实际的物理接口划分为多个逻辑上的接口,这些被划分的逻辑接口就是子接口。 二、适用情况 用在没有三层交换机,却要实现不同VLAN之间的互…

element ui中el-form-item的属性rules的用法

目录 el-form-item的属性rules的用法 栗子 总结 实践应用 一、 定义静态的校验规则 二、定义动态的校验规则 el-form-item的属性rules的用法 在学习element ui 的Form表单组件时&#xff0c;学到el-form-item也有rules属性&#xff0c;但是对应这个属性如何使用&#x…

【linux】服务器安装及卸载pycharm社区版教程

【linux】服务器安装及卸载pycharm社区版教程 【创作不易&#xff0c;求点赞关注收藏】 文章目录 【linux】服务器安装及卸载pycharm社区版教程1、到官网下载安装包2、通过终端wget下载安装包3、解压4、安装5、设置环境变量6、运行pycharm7、删除pycharm安装包、卸载pycharm …

前端JS特效第34集:jQuery俩张图片局部放大预览插件

jQuery俩张图片局部放大预览插件&#xff0c;先来看看效果&#xff1a; 部分核心的代码如下(全部代码在文章末尾)&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta http-equiv"X-UA-Co…

大数据环境下的房地产数据分析与预测研究的设计与实现

1绪论 1.1研究背景及意义 随着经济的快速发展和城市化进程的推进&#xff0c;房地产市场成为了国民经济的重要组成部分。在中国&#xff0c;房地产行业对经济增长、就业创造和资本投资起到了重要的支撑作用。作为中国西南地区的重要城市&#xff0c;昆明的房地产市场也备受关…

新版本 idea 创建不了 spring boot 2 【没有jkd8选项】

创建新项目 将地址换成如下 https://start.aliyun.com/

VmWare虚拟机开启wordpress服务并通过服务器内网穿透访问

目录 Ubuntu安装LAMP环境Ubuntu安装wordpress内网穿透服务端客户端 部分问题记录浏览器访问8000端口始终重定向到80端口修改站点url后wordpress无法登陆 曾经在阿里云的服务器上开了个wordpress服务作为个人博客&#xff0c;后来因为域名和服务器续费变贵没有继续续费&#xff…

【螺旋矩阵 II】python刷题记录

本来想着根据上一篇文章中大佬的思路来顺时针转转来改代码&#xff0c;想了一天没搞出来 http://t.csdnimg.cn/7sM8g 于是看了代码随想录的视频 一入循环深似海 | LeetCode&#xff1a;59.螺旋矩阵II_哔哩哔哩_bilibili 总结一下&#xff1a; 模拟的思路&#xff0c;每一圈…

解读|http和https的区别,谁更好用

在日常我们浏览网页时&#xff0c;有些网站会看到www前面是http&#xff0c;有些是https&#xff0c;这两种有什么区别呢&#xff1f;为什么单单多了“s”&#xff0c;会有人说这个网页会更安全些&#xff1f; HTTP&#xff08;超文本传输协议&#xff09;和HTTPS&#xff08;…

筑牢安全防线,促进企业稳定成长

注重技术创新与产业升级&#xff0c;还要高度重视安全管理与可持续发展&#xff0c;才能确保产业园区内企业能够在安全、稳定的环境中持续成长&#xff0c;并为社会和环境做出贡献。 强化安全管理 1、智能化安保系统&#xff1a; 园区采用智能化管理系统&#xff0c;实现对物…