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;}@Nullablepublic 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.mzph.cn/diannao/46954.shtml

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

相关文章

TCP与UDP的理解

文章目录 UDP协议UDP协议的特点UDP的应用以及杂项 TCP协议TCP协议段格式解释和TCP过程详解确认应答机制 -- 序号和确认序号以及6位标志位中的ACK超时重传机制连接管理机制 与标志位SYN,FIN,ACK滑动窗口流量控制拥塞控制延迟应答捎带应答和面向字节流粘包问题TCP异常情况TCP特点…

5.4 软件工程-系统设计

系统设计 - 概述 设计软件系统总体结构 数据结构及数据库设计 编写概要设计文档、评审 详细设计的基本任务 真题

anaconda常用指令学习

在win系统下安装完成后,需要进行环境变量的配置 配置环境变量,把下面的4个目录全部加到PATH变量里面。 Anaconda安装目录的根目录\ Anaconda安装目录的根目录\Scripts Anaconda安装目录的根目录\Library\bin Anaconda安装目录的根目录\Library\mingw-w6…

[米联客-安路飞龙DR1-FPSOC] FPGA基础篇连载-15 SPI接收程序设计

软件版本:Anlogic -TD5.9.1-DR1_ES1.1 操作系统:WIN10 64bit 硬件平台:适用安路(Anlogic)FPGA 实验平台:米联客-MLK-L1-CZ06-DR1M90G开发板 板卡获取平台:https://milianke.tmall.com/ 登录“米联客”FPGA社区 ht…

Michael.W基于Foundry精读Openzeppelin第64期——UUPSUpgradeable.sol

Michael.W基于Foundry精读Openzeppelin第64期——UUPSUpgradeable.sol 0. 版本0.1 UUPSUpgradeable.sol 1. 目标合约2. 代码精读2.1 modifier onlyProxy()2.2 modifier notDelegated()2.3 proxiableUUID()2.4 upgradeTo(address newImplementation) && _authorizeUpgra…

linux服务器登录mysql无异常,本地登录报1045 -Access denied for user

1、本地登录linux服务器报“1045 -Access denied for user (用户访问被拒绝)” 造成上面链接问题的原因是,用户权限不足,需要在linux服务器上执行如下2条命令即可 CREATE USER root127.0.0.1 IDENTIFIED BY root123; GRANT ALL P…

新美业和传统美业的区别在哪些方面?连锁美业SaaS收银系统源码

新美业和传统美业在很多方面存在着显著的区别。传统美业通常指的是传统的美容美发行业,而新美业则更多地与科技、数字化和创新相关。随着科技的不断发展和消费者需求的变化,新美业将继续引领美容行业的发展趋势。 以下是传统美业和新美业之间的一些区别…

用 AI 写歌词,让音乐表达与众不同

在音乐的广袤天地中,我们都渴望通过独特的表达来触动人心,展现自我。而如今,AI 技术的崛起为音乐创作带来了全新的突破,让我们能够以一种前所未有的方式赋予音乐独特的灵魂。 “妙笔生词智能写歌词软件(veve522&#…

Docker缩小镜像体积与搭建LNMP架构

镜像加速地址 {"registry-mirrors": ["https://docker.m.daocloud.io","https://docker.1panel.live"] } daemon.json 配置文件里面 bip 配置项中可以配置docker 的网段 {"graph": "/data/docker", #数据目录&#xff0…

领航Linux UDP:构建高效网络新纪元

欢迎来到 破晓的历程的 博客 ⛺️不负时光,不负己✈️ 文章目录 引言Udp和Tcp的异同相同点不同点总结 1.1、socket1.2、bind1.3、recvfrom1.4、sendto2.1、代码2.1、说明3.1、代码3.2、说明 引言 在前几篇博客中,我们学习了Linux网络编程中的一些概念。…

【数据结构(邓俊辉)学习笔记】高级搜索树02——B树

文章目录 1. 大数据1.1 640 KB1.2 越来越大的数据1.3 越来越小的内存1.4 一秒与一天1.5 分级I/O1.6 1B 1KB 2. 结构2.1 观察体验2.2 多路平衡2.3 还是I/O2.4 深度统一2.5 阶次含义2.6 紧凑表示2.7 BTNode2.8 BTree 3. 查找3.1 算法过程3.2 操作实例3.3 算法实现3.4 主次成本3.…

JAVASE——图书管理系统

JAVASE图书管理系统 主要业务有:管理员(增删改查),会员(借书还书查看记录) 管理员主要有:查看图书,增加图书,修改图书,会员管理,删除图书, 会员主要有&#x…

昇思25天学习打卡营第22天|GAN图像生成

今天是参加昇思25天学习打卡营的第22天,今天打卡的课程是“GAN图像生成”,这里做一个简单的分享。 1.简介 今天来学习“GAN图像生成”,这是一个基础的生成式模型。 生成式对抗网络(Generative Adversarial Networks,GAN)是一种…

Bug:时间字段显示有问题

Bug:时间字段显示有问题 文章目录 Bug:时间字段显示有问题1、问题2、解决方法一:添加注解3、解决方法二:消息转换器自定义对象映射器配置消息转换器 1、问题 ​ 在后端传输时间给前端的时候,发现前端的时间显示有问题…

[Spring] Spring Web MVC案例实战

🌸个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 🏵️热门专栏: 🧊 Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 🍕 Collection与…

AV1技术学习:Translational Motion Compensation

编码块根据运动矢量在参考帧中找到相应的预测块,如下图所示,当前块的左上角的位置为(x0, y0),在参考帧中找到同样位置(x0, y0)的块,根据运动矢量移动到目标参考块(左上角位置为:(x1, y1))。 AV1…

前端a-tree遇到的问题

在使用a-tree时候,给虚拟滚动的高度,然后展开a-tree滑动一段距离 比如这样 随后你切换页面,在返回这个页面的时候 就会出现这样的bug 解决方法: onBeforeRouteLeave((to, from, next) > {// 可以在路由参数变化时执行的逻辑ke…

白山云荣获信通院“算网安全行业应用优秀案例”奖

日前,在由中国通信标准化协会算网融合产业及标准推进委员会与信通院共同组织召开的“2024年算网融合产业发展大会”上,白山云凭借创新的SD-WAN算网融合方案,荣获“算网安全行业应用优秀案例”奖。 算网融合是多元异构、海量泛在的算力设施&am…

path模块和HTTP协议

一。path模块常用API ./相对路径,/绝对路径 二,HTTP协议 1.请求报文 1.请求行 URL的组成 2.请求头 3.请求体 可以是空:GET请求 可以是字符串,还可以是json:POST请求 2.响应报文 1.响应行 HTTP / 1.1 200 OK H…

VsCode 与远程服务器 ssh免密登录

首先配置信息 加入下列信息 Host qb-zn HostName 8.1xxx.2xx.3xx User root ForwardAgent yes Port 22 IdentityFile ~/.ssh/id_rsa 找到自己的公钥,不带pub是私钥,打死都不能给别人。复制公钥 拿到公钥后,来到远程服务器 vim ~/.ss…