关于springboot的Rest请求映射处理的源码分析(一)

news2024/9/26 1:22:32

我们在开发中很常见的一种方式是通过请求的类型,也就是restful风格来区别我们的请求接口。
通过请求类型的不同(GET POST PUT DELETE) 来区分即便是请求路径相同的请求。
但是他的底层是如何支持的呢,明明我请求路径都是/user。就因为类型不同就能区分到不同的接口。
接下来我们就看看这部分实现。从这个实现开始,我们将会彻底打通关于springboot中的请求处理的所有原理。

一、准备测试代码

1、简易前端

我的前端属于是菜的不能再菜那种,这里我们就实现四个简单的提交表单,用来调用我们的四个接口,从而观察现象。
我们把这个html文件命名为restfulForm.html放在我们的静态资源目录下面。
在这里插入图片描述
页面的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Form</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
        }
        .container {
            max-width: 600px;
            margin: auto;
        }
        form {
            display: flex;
            flex-direction: column;
        }
        label {
            margin-bottom: 5px;
            font-weight: bold;
        }
        input, textarea {
            margin-bottom: 15px;
            padding: 8px;
            font-size: 16px;
        }
        button {
            padding: 10px;
            font-size: 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
        button:hover {
            background-color: #45a049;
        }
    </style>
</head>
<body>
<div class="container">
    <h1>Test Form</h1>
    <form action="/user" method="get">
        <input value="get提交" type="submit">
    </form>
    <form action="/user" method="post">
        <input value="post提交" type="submit">
    </form>
    <form action="/user" method="put">
        <input value="put提交" type="submit">
    </form>
    <form action="/user" method="delete">
        <input value="delete提交" type="submit">
    </form>
</div>
</body>
</html>

在这个页面中,我们就准备了四个表单提交。分别对应四个不同的接口,他们的名字都是一样的,但是其请求类型不同。

2、后端restful接口

@org.springframework.web.bind.annotation.RestController
public class RestController {


	@RequestMapping(value = "/user" ,method = RequestMethod.GET)
	public String getUser() {
		return "getUser";
	}

	@RequestMapping(value = "/user" ,method = RequestMethod.POST)
	public String postUser() {
		return "postUser";
	}

	@RequestMapping(value = "/user" ,method = RequestMethod.PUT)
	public String putUser() {
		return "putUser";
	}

	@RequestMapping(value = "/user" ,method = RequestMethod.DELETE)
	public String delUser() {
		return "delUser";
	}
}

这四个接口,名字路径都一样,但是method类型不同。这正是我们要测试的。

3、测试结果

3.1、get请求

点击get按钮,返回如下。结果没毛病。
在这里插入图片描述

3.2、post请求

点击post按钮,返回如下。结果没毛病。
在这里插入图片描述

3.3、put请求

点击put请求,返回如下。
在这里插入图片描述
出问题了,为啥返回的还是getUser。

3.4、delete请求

点击delete请求,返回如下。
在这里插入图片描述
同样的问题。

4、测试问题总结

我们看到在put和delete请求的时候,出现了我们预想之外的结果。这个不对,难道是我们有没考虑到的问题吗。

5、问题分析解决

我们以前用MVC的时候要想实现rest的请求,是需要配置一个HiddenHttpMethodFilter的过滤器的。具体使用可以看这个文档。MVC使用Restful接口
那人家MVC能解决,你来boot这里封装了MVC咋还出问题了,所以我们来看看BOOT是怎么封装的呢。
我们知道boot中关于MVC的操作都是配置在WebMvcAutoConfiguration这个配置类的。我们找到这个类。
巧的是刚打开这个类我们就看到了关于mvc中的那个HiddenHttpMethodFilter,只不过boot继承了他一下,弄了个OrderedHiddenHttpMethodFilter 出来,就是多了个排序功能,本质实现还一样。

/**
 * 这个是注册你对于restful风格请求的映射,当然默认是GET,但是你不配delete put是不能生效的,他会映射去你相同名字的get请求
 * 没有相同名字的get就404了  注意这个是表单提交的rest风格过滤器,你要是用postman这个不生效,因为表单只能写get post
 * 不能写delete 和 put,这里你要是原生的其实没事,这里只是为了兼容表单的
 * @return
 */
@Bean
// 没这个才生效这个配置,我们没自己写,所以生效
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
// 配置文件里面没有配置spring.mvc.hiddenmethod.filter.enabled=true,所以不生效,所以我们要想生效rest风格,需要配置这个
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	// 点进去看他的过滤规则
	/**
	 * if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
	 * 			String paramValue = request.getParameter(this.methodParam);
	 * 			if (StringUtils.hasLength(paramValue)) {
	 * 				String method = paramValue.toUpperCase(Locale.ENGLISH);
	 * 				if (ALLOWED_METHODS.contains(method)) {
	 * 					requestToUse = new HttpMethodRequestWrapper(request, method);
	 * 				                }            * 			}
	 * 		}
	 * 	filterChain.doFilter(requestToUse, response);放行过滤器
	 * 	这是他过滤器的实现,我们看到你的方法必须是post,并且没有异常,然后参数里面必须有一个_method参数,这个参数的值就是你的请求方法
	 * 	也就是你的表单要写一个name="_method" value="delete"然后他会读这个_method",ALLOWED_METHODS这个是个数组就是put get delete post
	 * 	那些支持的rest类型的接口,这里拿到再去给你路由,过滤器给你把你原来的get请求拿到,然后给你包装成了delete请求,然后你就可以处理delete请求了
	 */
	return new OrderedHiddenHttpMethodFilter();
}

我顺手加了一些注释,但是你可以忽视这些注释,因为下面的原理我们会提到这些东西。
所以我们看到他有一个生效注解:

@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)

他的意思是当我们的配置文件中配了spring.mvc.hiddenmethod.filter的时候他才会生效,如果没配,他也不会帮我们matchIfMissing 。所以我们需要配置一下。
他的规则是前缀是spring.mvc.hiddenmethod.filter,然后name是enabled。所以我们的配置就是
spring.mvc.hiddenmethod.filter.enabled=true
我们再来试一试:
依然是这样。
在这里插入图片描述
难道还有我们不知道的玄机吗,既然我们配置了,那我们再去OrderedHiddenHttpMethodFilter这个类里面去看看。这个类啥也没有,就是继承了HiddenHttpMethodFilter,我们就直接去看HiddenHttpMethodFilter。

public class HiddenHttpMethodFilter extends OncePerRequestFilter {

	// 这里放了一个PUT和一个DELETE的类型,那个PATCH不常用
	private static final List<String> ALLOWED_METHODS =
			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
					HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));

	// 这里有个字符串_method
	public static final String DEFAULT_METHOD_PARAM = "_method";
	// 这个其实还是_method字符串,套着引用了一下,无聊,可能用在不同地方表示不同的业务属性,
	// 名字区分一下吧
	private String methodParam = DEFAULT_METHOD_PARAM;


	/**
	 * Set the parameter name to look for HTTP methods.
	 * @see #DEFAULT_METHOD_PARAM
	 * 这里应该是让用户自己扩展的,你可以传入一些参数来替代_method,回头再说
	 */
	public void setMethodParam(String methodParam) {
		Assert.hasText(methodParam, "'methodParam' must not be empty");
		this.methodParam = methodParam;
	}
	
	/**
		既然这是个过滤器类,那他的doFilterInternal看命名一定就是干活的了
	*/
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
			throws ServletException, IOException {
		// 获取这个servlet请求信息
		HttpServletRequest requestToUse = request;
		// 首先你的请求必须是POST请求,他才处理,而且你的请求信息里面不能有javax.servlet.error.exception
		if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
			// 取出请求里面包着的_method属性的值
			String paramValue = request.getParameter(this.methodParam);
			if (StringUtils.hasLength(paramValue)) {
				// 一律转小写 
				String method = paramValue.toUpperCase(Locale.ENGLISH);
				// 如果你的这个_method里面的内容在delete和put里面,他就会给你处理,然后把你
				// 的请求包装一下
				if (ALLOWED_METHODS.contains(method)) {
					requestToUse = new HttpMethodRequestWrapper(request, method);
				}
			}
		}

		filterChain.doFilter(requestToUse, response);
	}
}

所以我们看到如果你的这个_method里面的内容在delete和put里面,他就会给你处理,然后把你的请求包装一下,然后传递下去,我们虽然还不知道他这里到底有何区别,但是我们看到了他这里处理了delete和put的逻辑。而且他是取出你的_method属性对应的值去看看你在不在delete和put里面,但是你的method本身还是POST,PUT也是一样的。 所以我们的表单需要变成这样。

<form action="/user" method="POST">
     <input value="put提交" type="submit">
     <input name="_method" value="PUT">
 </form>
 <form action="/user" method="POST">
     <input value="delete提交" type="submit">
     <input name="_method" value="DELETE">
 </form>

但是我们看到页面变成这样了。
在这里插入图片描述
好家伙太丑了,实际上我们新加的那个没啥用,就是一个提交个参数,用户不用看到,我们给他隐藏了提交就行。我们变成这样。

<form action="/user" method="POST">
    <input value="put提交" type="submit">
    <input name="_method" type="hidden" value="PUT">
</form>
<form action="/user" method="POST">
    <input value="delete提交" type="submit">
    <input name="_method" type="hidden" value="DELETE">
</form>

隐藏了加一个type=“hidden”,此时就可以了。
在这里插入图片描述
在这里插入图片描述
此时我们看到就都正常了。
此时就符合了我们的预期,下面我们就来看看到底是啥原理。

二、源码分析

其实上面我们基本已经梳理清楚了,因为表单提交只能提交GET POST,不支持delete和put,所以我们通过加了一个参数,并且请求类型都是POST,让那段代码生效,把我们原来的POST请求给包装成了delete和put请求。这样就能生效了,至于为啥包装好了,就能找到对应的controller方法,这个我们后面再说。
但是呢,注意一点,这个只是给表单生效的。如果我们用postman或者curl或者别人从其他项目直接发这类请求是不走这个逻辑的,人家从http那边直接就是delete和put请求了。不用你再转了,表单是不支持这个,所以才要包装一层。而且现在都是前后端分离了,前端项目直接请求也不用走这个逻辑。只有在springboot的表单才有用。所以你要是前后端分离,或者postman,安卓一类的,不需要配置
spring.mvc.hiddenmethod.filter.enabled=true,压根没这回事。

三、扩展点

我们上面在看源码的时候我说了两个事情。
1、@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter()
OrderedHiddenHttpMethodFilter 这个组件是在容器里面没有HiddenHttpMethodFilter才生效的,所以我们可以自己写一个覆盖了他的。
2、HiddenHttpMethodFilter 中有一个属性就是methodParam ,他默认是_method,然后你表单提交一个_method才能被识别,但是我们自己写了HiddenHttpMethodFilter,完全可以不用他这个_method,我们自己改一个比如叫cjb,这样我们的表单要是改成cjb不也行吗,这是一种自己定义的风格,看你想用啥就改成啥,二话不说,我们来实践一把。

  • 添加一个自己的HiddenHttpMethodFilter
@Configuration
public class MyHiddenHttpMethodFilterConfig{
	
	@Bean
    public HiddenHttpMethodFilter myHiddenHttpMethodFilter(){
		HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
		// 我们给它赋值一个我们自己的属性
		hiddenHttpMethodFilter.setMethodParam("cjb");
		return hiddenHttpMethodFilter;
    }

}
  • 修改页面那个隐藏参数为我们自己定义的cjb
<form action="/user" method="POST">
    <input value="put提交" type="submit">
    <input name="cjb" type="hidden" value="PUT">
</form>
<form action="/user" method="POST">
    <input value="delete提交" type="submit">
    <input name="cjb" type="hidden" value="DELETE">
</form>

测试一把,没有问题。
在这里插入图片描述

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

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

相关文章

网络层 III(划分子网和构造超网)【★★★★★★】

&#xff08;★★&#xff09;代表非常重要的知识点&#xff0c;&#xff08;★&#xff09;代表重要的知识点。 一、网络层转发分组的过程 分组转发都是基于目的主机所在网络的&#xff0c;这是因为互联网上的网络数远小于主机数&#xff0c;这样可以极大地压缩转发表的大小。…

【Python报错】AttributeError`:`‘NoneType‘ object has no attribute ‘XXXX‘`

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引言 在Python编程中&#xff0c;AttributeError是一个常见的错误类型&#xff0c;它表示尝试访问的对象没有该属性。本文将探讨…

深度强化学习算法(六)(附带MATLAB程序)

深度强化学习&#xff08;Deep Reinforcement Learning, DRL&#xff09;结合了深度学习和强化学习的优点&#xff0c;能够处理具有高维状态和动作空间的复杂任务。它的核心思想是利用深度神经网络来逼近强化学习中的策略函数和价值函数&#xff0c;从而提高学习能力和决策效率…

CNN网络的一些基本知识

CNN 网络的layer介绍 在卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;中&#xff0c;往往包含许多种不同的网络层交替组成&#xff0c;主要有卷积层&#xff08;Convolutional Layer&#xff09;、池化层&#xff08;Pooling Layer&#…

《黑神话:悟空》爆火,对程序员的 5 点启示(2)

前言 继续上篇未完章节…… 4. 需求捕捉 需求有真需求和伪需求的区别&#xff0c;捕捉和理解用户的真需求对于产品非常重要。 在《黑神话&#xff1a;悟空》面世以后&#xff0c;很多玩家都不吝称赞&#xff0c;有玩家这么评论&#xff1a; 不吹牛逼&#xff0c;这一段我眼…

C#中通过TabControl控制MDI子窗体显示切换的实现过程

类似excel表格中各个表单sheet的切换效果&#xff0c;使用tabcontrol控件实现类似的功能。效果如下&#xff1a; 过程涉及父窗体MDIParent1、子窗体main、自定义基础功能类MdiChildBase。 基础功能类MdiChildBase继承自Form创建&#xff0c;定义了一个委托SetTabControlDelega…

apisix 本地开发环境部署

apisix 本地开发环境部署 本地开发环境部署可以采用 docker-compose 部署&#xff0c;配置文件如下 apisix 配置文件 apisix:node_listen: 9080 # APISIX 节点监听地址enable_ipv6: falsehttp:port: 9080 # APISIX HTTP 端口#https:# port: 9443 # APISIX HTTPS 端口# ssl…

《机器学习》K-means 聚类 原理、参数解析、案例实现

1. 引言 随着数据的快速增长和复杂性的不断提高,如何从大量数据中提取有用信息已成为数据科学和人工智能领域的核心挑战。聚类作为一种无监督学习方法,通过将数据分为若干组,使得同一组内的样本具有较高的相似性,而不同组之间的样本差异显著。这种方法被广泛应用于数据分析…

探索极速Python:Sanic框架的魔力

文章目录 探索极速Python&#xff1a;Sanic框架的魔力背景&#xff1a;为什么选择Sanic&#xff1f;Sanic是什么&#xff1f;如何安装Sanic&#xff1f;简单的库函数使用方法场景应用示例常见Bug及解决方案总结 探索极速Python&#xff1a;Sanic框架的魔力 背景&#xff1a;为什…

带你了解RS485通讯网关-天拓四方

在当今工业自动化和智能化的浪潮中&#xff0c;高效、可靠的数据通讯是确保系统运行顺畅的关键。RS485通讯网关作为工业通讯网络中的核心设备&#xff0c;承担着数据传输的重要任务。本文将从RS485通讯网关的技术背景、功能特性、应用场景以及选购要点等方面进行深入探讨&#…

【智能算法应用】基于融合改进A星-麻雀搜索算法求解六边形栅格地图路径规划

目录 1.算法原理2.结果展示3.参考文献4.代码获取 1.算法原理 【智能算法】麻雀搜索算法&#xff08;SSA&#xff09;原理及实现 六边形栅格地图 分析一下地图&#xff1a; 六边形栅格地图上移动可以看做6领域运动&#xff0c;偶数列与奇数列移动方式有所差异&#xff0c;将六…

[每周一更]-(第112期):WDL(Workflow Description Language)说明及使用示例

文章目录 什么是WDL&#xff1f;WDL的基本结构示例 WDL 文件示例任务&#xff1a;字符串反转示例工作流&#xff1a;字符串处理 WDL 文件详解任务&#xff08;Task&#xff09;工作流&#xff08;Workflow&#xff09; 运行示例总结报错参考 什么是WDL&#xff1f; WDL&#x…

Java图形用户界面之Applet设计

Applet设计 前言一、Applet的基本工作原理与使用局限Applet的基本工作原理代码示例 Java Applet 的使用局限Java沙箱安全机制原理 Applet的生命周期与运行方式 二、Applet类Applet类的层次结构常用方法生命周期方法显示方法多媒体支持方法其他方法 三、Applet和GUI基于AWT和Swi…

字符操作函数和内存操作函数

1 字符操作函数 1.strlen size_t strlen ( const char * str ); 函数功能&#xff1a;返回以\0结尾的字符串长度&#xff0c;不包含\0。 注&#xff1a;strlen返回值是一个无符号整数size_t。 #include <stdio.h>int main() {const char*str1 "abcdef";const…

Linux基础软件-selinux库文件swap

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 Linux进阶部分又分了很多小的部分,我们刚讲完了Linux日常运维。讲的那些东西都算是系统自带的&#xff0c;但是Linux作为一个…

LVGL 控件之圆弧(lv_arc)

目录 一、圆弧部件1、部件组成2、lv_art_t3、圆弧部件角度设置4、圆弧部件旋转设置5、圆弧的模式选择6、圆弧部件的变化率设置7、移除旋钮8、事件9、获取/设置信息相关的 API 二、例程 一、圆弧部件 1、部件组成 圆弧&#xff08;lv_arc&#xff09;部件由三个部分组成&#…

学习之SQL语言之DDL

查询 查询所有数据 SHOW DATABASES&#xff1b; 查询当前数据库 SELECT DATABASE(); 创建 CREATE DATABASE IF NOT EXISTS 数据库名&#xff1b; 删除 DROP DATABASE IF EXISTS 数据库名&#xff1b; 使用 USE 数据库名&#xff1b; 查询当前数据库所有表 SHOW TABLES; 查…

优化大型语言模型微调:MoLA层级专家分配策略

人工智能咨询培训老师叶梓 转载标明出处 大模型&#xff08;LLMs&#xff09;的微调过程中&#xff0c;计算资源的需求巨大&#xff0c;这促使研究者们探索参数高效微调&#xff08;PEFT&#xff09;技术。低秩适应&#xff08;LoRA&#xff09;和专家混合模型&#xff08;MoE…

超分 Real-ESRGAN 使用笔记

效果图 目录 依赖项: 视频推理入口: 图片推理入口文件: RealESRGAN_x4plus 12g显存不够 RealESRGAN_x4plus_anime_6B 的效果 树枝之间产生了蒙版 RealESRNet_x4plus 有点模糊 2022年开源的 GitHub - xinntao/Real-ESRGAN: Real-ESRGAN aims at developing Practical…

操作系统 --王道计算机考研--学习笔记

文章目录 前言第一章 引言1.1 什么是操作系统&#xff1f;1 操作系统作为虚拟机2 操作系统作为资源管理者3 为上层提供服务-- 系统调用 2.操作系统特征与发展分类2.1 操作系统特征1.并发2.共享3.虚拟4.异步 2.2 操作系统发展和分类 3.操作系统的的运行机制和中断异常、系统调用…