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

2024-08-30 20:28

本文主要是介绍关于springboot的Rest请求映射处理的源码分析(一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

我们在开发中很常见的一种方式是通过请求的类型,也就是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()));// 这里有个字符串_methodpublic 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看命名一定就是干活的了*/@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {// 获取这个servlet请求信息HttpServletRequest requestToUse = request;// 首先你的请求必须是POST请求,他才处理,而且你的请求信息里面不能有javax.servlet.error.exceptionif ("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{@Beanpublic 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>

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

这篇关于关于springboot的Rest请求映射处理的源码分析(一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1121841

相关文章

Java实现检查多个时间段是否有重合

《Java实现检查多个时间段是否有重合》这篇文章主要为大家详细介绍了如何使用Java实现检查多个时间段是否有重合,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录流程概述步骤详解China编程步骤1:定义时间段类步骤2:添加时间段步骤3:检查时间段是否有重合步骤4:输出结果示例代码结语作

Java中String字符串使用避坑指南

《Java中String字符串使用避坑指南》Java中的String字符串是我们日常编程中用得最多的类之一,看似简单的String使用,却隐藏着不少“坑”,如果不注意,可能会导致性能问题、意外的错误容... 目录8个避坑点如下:1. 字符串的不可变性:每次修改都创建新对象2. 使用 == 比较字符串,陷阱满

Java判断多个时间段是否重合的方法小结

《Java判断多个时间段是否重合的方法小结》这篇文章主要为大家详细介绍了Java中判断多个时间段是否重合的方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录判断多个时间段是否有间隔判断时间段集合是否与某时间段重合判断多个时间段是否有间隔实体类内容public class D

IDEA编译报错“java: 常量字符串过长”的原因及解决方法

《IDEA编译报错“java:常量字符串过长”的原因及解决方法》今天在开发过程中,由于尝试将一个文件的Base64字符串设置为常量,结果导致IDEA编译的时候出现了如下报错java:常量字符串过长,... 目录一、问题描述二、问题原因2.1 理论角度2.2 源码角度三、解决方案解决方案①:StringBui

Java覆盖第三方jar包中的某一个类的实现方法

《Java覆盖第三方jar包中的某一个类的实现方法》在我们日常的开发中,经常需要使用第三方的jar包,有时候我们会发现第三方的jar包中的某一个类有问题,或者我们需要定制化修改其中的逻辑,那么应该如何... 目录一、需求描述二、示例描述三、操作步骤四、验证结果五、实现原理一、需求描述需求描述如下:需要在

Java中ArrayList和LinkedList有什么区别举例详解

《Java中ArrayList和LinkedList有什么区别举例详解》:本文主要介绍Java中ArrayList和LinkedList区别的相关资料,包括数据结构特性、核心操作性能、内存与GC影... 目录一、底层数据结构二、核心操作性能对比三、内存与 GC 影响四、扩容机制五、线程安全与并发方案六、工程

JavaScript中的reduce方法执行过程、使用场景及进阶用法

《JavaScript中的reduce方法执行过程、使用场景及进阶用法》:本文主要介绍JavaScript中的reduce方法执行过程、使用场景及进阶用法的相关资料,reduce是JavaScri... 目录1. 什么是reduce2. reduce语法2.1 语法2.2 参数说明3. reduce执行过程

如何使用Java实现请求deepseek

《如何使用Java实现请求deepseek》这篇文章主要为大家详细介绍了如何使用Java实现请求deepseek功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1.deepseek的api创建2.Java实现请求deepseek2.1 pom文件2.2 json转化文件2.2

Java调用DeepSeek API的最佳实践及详细代码示例

《Java调用DeepSeekAPI的最佳实践及详细代码示例》:本文主要介绍如何使用Java调用DeepSeekAPI,包括获取API密钥、添加HTTP客户端依赖、创建HTTP请求、处理响应、... 目录1. 获取API密钥2. 添加HTTP客户端依赖3. 创建HTTP请求4. 处理响应5. 错误处理6.

Spring AI集成DeepSeek的详细步骤

《SpringAI集成DeepSeek的详细步骤》DeepSeek作为一款卓越的国产AI模型,越来越多的公司考虑在自己的应用中集成,对于Java应用来说,我们可以借助SpringAI集成DeepSe... 目录DeepSeek 介绍Spring AI 是什么?1、环境准备2、构建项目2.1、pom依赖2.2