从 01 开始 从 01 开始
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档

晓林

程序猿,自由职业者,博主,英语爱好者,健身达人
首页
  • 📚 计算机基础

    • 计算机简史
    • 数字电路
    • 计算机组成原理
    • 操作系统
    • Linux
    • 计算机网络
    • 数据库
    • 编程工具
    • 装机
  • 🎨 前端

    • Node
  • JavaSE
  • Java 高级
  • JavaEE

    • 构建、依赖管理
    • Ant
    • Maven
    • 日志框架
    • Junit
    • JDBC
    • XML-JSON
  • JavaWeb

    • 服务器软件
    • 环境管理和配置管理-科普篇
    • Servlet
  • Spring

    • Spring基础
  • 主流框架

    • Redis
    • Mybatis
    • Lucene
    • Elasticsearch
    • RabbitMQ
    • MyCat
    • Lombok
  • SpringMVC

    • SpringMVC 基础
  • SpringBoot

    • SpringBoot 基础
  • Windows 使用技巧
  • 手机
  • 最全面的输入法教程
  • 最全面的浏览器教程
  • Office
  • 图片类工具
  • 效率类工具
  • RSS
  • 码字工具
  • 各大平台
  • 校招
  • 五险一金等
  • 职场规划
  • 关于离职
  • 杂谈
  • 📖 读书

    • 读书工具
    • 读书笔记
  • 🌍 英语

    • 从零开始学英语
    • 英语兔的相关视频
    • Larry 想做技术大佬的相关视频
  • 🏛️ 政治

    • 反腐
    • GFW
    • 404 内容
    • 审查与自我审查
    • 互联网
    • 战争
  • 💰 经济

    • 关于税
    • 理财
  • 💪 健身

    • 睡眠
    • 皮肤
    • 口腔健康
    • 学会呼吸
    • 健身日志
  • 🏠 其他

    • 驾驶技能
    • 租房与买房
    • 厨艺
  • 电影

    • 电影推荐
  • 电视剧
  • 漫画

    • 漫画软件
    • 漫画推荐
  • 游戏

    • Steam
    • 三国杀
    • 求生之路
  • 小说
  • 关于本站
  • 关于博主
  • 打赏
  • 网站动态
  • 友人帐
  • 从零开始搭建博客
  • 搭建邮件服务器
  • 本站分享
  • 🌈 生活

    • 2022
    • 2023
    • 2024
    • 2025
  • 📇 文章索引

    • 文章分类
    • 文章归档
  • JavaSE

  • JavaSenior

  • JavaEE

  • JavaWeb

  • Spring

  • 主流框架

  • SpringMVC

  • SpringBoot

    • SpringBoot教程-尚硅谷

      • SpringBoot 课程介绍
      • Spring 和 SpringBoot
      • HelloWorld
      • 了解自动配置原理
      • 底层注解-@Configuration 详解
      • 底层注解-@Import 导入组件
      • 底层注解-@Conditional 条件装配
      • 原生配置文件引入-@ImportResource
      • 底层注解-配置绑定 @ConfigurationProperties
      • 自动配置原理
      • 自动配置流程
      • Lombok 简化开发
      • DevTools
      • Spring-Initailizr
      • 配置文件-Yaml 用法
      • Web 开发简介
      • web 开发-静态资源规则于定制化
      • 静态资源配置原理
      • Rest 映射及源码解析
        • 什么是 REST 风格的 HTTP 请求
        • SpringBoot 中的 HttpMethodFilter
        • 开启配置
        • 添加处理方法
        • 添加表单
        • 测试
        • 源码分析
        • 直接发送 REST 请求
        • 改变默认参数
        • 源码
      • 请求映射原理
      • 常用参数注解使用
      • MatrixVariable:矩阵变量
      • 各种类型参数解析原理
      • Servlet-API 参数解析原理
      • Model、Map 参数解析原理
      • 自定义对象参数绑定原理
      • 自定义 Converter 原理
      • 数据响应原理
      • 内容协商原理
      • 基于请求参数的内容原理
      • 自定义 MessageConverter 原理
      • Thymeleaf 初体验
      • web 实验-后台管理系统
      • web 实验-抽取公共页面
      • web 实验-遍历数据
      • 源码分析-视图解析器与视图
      • 拦截器-登录检查与静态资源放行
      • 拦截器的执行时机和原理
      • 单文件和多文件上传的使用
      • 文件上传原理
      • 错误处理机制
      • 错误处理-底层组件源码分析
      • 异常处理流程
      • 几种异常处理原理
      • Web 原生对象注入
      • 嵌入式 Servlet 容器
      • 定制化原理
      • 数据库场景的自动配置分析和整合测试
      • 自定义方式整合 Druid
      • 通过 starter 整合 Druid
      • 整合 Mybatis
      • 使用注解整合 Mybatis
      • 整合 MybatisPlus 操作数据库
      • MybatisPlus-列表分页展示
      • 整合 Redis
      • 单元测试-Junit5
      • 单元测试-断言机制
      • 单元测试-前置条件
      • 单元测试-嵌套测试
      • 单元测试-参数化测试
      • 指标监控-基本概念
      • 指标监控-配置 EndPoint
      • 指标监控-可视化
      • 原理解析-Profile 功能
      • 配置文件深入
      • 自定义 Starter
      • SpringApplication 初始化过程
      • SpringBoot 完整启动过程
  • Java
  • SpringBoot
  • SpringBoot教程-尚硅谷
2023-08-22
目录

Rest 映射及源码解析

# 190.Rest 映射及源码解析

SpringBoot 也支持 Rest 风格的 HTTP 请求

 ‍

# 什么是 REST 风格的 HTTP 请求

SpringBoot 支持 Rest 风格(使用 HTTP 请求方式动词来表示对资源的操作)

  • 以前的增删改查用户:请求的路径各不相同

    • /getUser:获取用户
    • /deleteUser:删除用户
    • /editUser:修改用户
    • /saveUser:保存用户
  • 现在的增删改查用户: 只请求/user 路径,但是 HTTP 请求方式不同:

    • GET:获取用户
    • DELETE:删除用户
    • PUT:修改用户
    • POST:保存用户 ‍ 之前我们学习 SpringMVC 的时候,讲过浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring3.0 添
      加了一个过滤器,可以将浏览器请求改为指定的请求方式,发送给我们的控制器方法,使得支持 GET、POST、PUT 与 DELETE 请求。

其原理是用 HiddentHttpMethodFilter,用过滤器的方式来完成(参考 SpringMVC 常用注解 (opens new window))。具体怎么实现的呢?其要求前端传一个_method 参数,该参数就是真正的请求方式;然后 filter 读取这个值,然后将该 HTTP 请求转为该方式的请求,然后才是 handler 处理请求 ‍

# SpringBoot 中的 HttpMethodFilter

在 SpringBoot 中,其实已经配置了 1 个 HiddentHttpMethodFilter:可以看自动配置类 WebMvcAutoConfiguration,有一个 OrderedHiddenHttpMethodFilter 的组件:

public class WebMvcAutoConfiguration {
    //.............
	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", 
                                    name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}
    //.............
1
2
3
4
5
6
7
8
9
10

‍

# 开启配置

通过注解 ConditionalOnProperty,可以看到默认值 false。因此,我们配置下开启 REST 方式:修改 application.yml 文件,添加如下内容:

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
1
2
3
4
5

‍

# 添加处理方法

我们来试试是否能用:

@RestController // @RestController = @Controller + @ResponseBody
public class HelloController {

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

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

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

    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    public String deleteUser(){
        return "DELETE-张三";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

‍ PS,可以简写:

@GetMapping("/user")
public String getUser(){
    return "GET-张三";
}

@PostMapping("/user")
public String saveUser(){
    return "POST-张三";
}

@PutMapping("/user")
public String putUser(){
    return "PUT-张三";
}

@DeleteMapping("/user")
public String deleteUser(){
    return "DELETE-张三";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

‍

# 添加表单

在 index.html 里添加表单:


<form action="/user" method="get">
    <input value="REST-GET提交" type="submit">
</form>

<form action="/user" method="post">
    <input value="REST-post提交" type="submit">
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <input value="REST-DELETE提交" type="submit">
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT">
    <input value="REST-PUT提交" type="submit">
</form>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

‍

# 测试

逐个点击按钮,可以看到能正常处理 REST 请求:

‍

# 源码分析

我们根据源码,来再次说明一下原理(读者也可以加个断点,通过逐步调试的方式来观察执行过程)

首先,WebMvcAutoConfiguration 类中,会返回一个 Bean,叫做 OrderedHiddenHttpMethodFilter:

@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	return new OrderedHiddenHttpMethodFilter();
}
1
2
3
4
5
6

‍ 而该类是继承了 HiddenHttpMethodFilter:

public class OrderedHiddenHttpMethodFilter extends HiddenHttpMethodFilter implements OrderedFilter {
	public static final int DEFAULT_ORDER = REQUEST_WRAPPER_FILTER_MAX_ORDER - 10000;

	private int order = DEFAULT_ORDER;

	@Override
	public int getOrder() {
		return this.order;
	}
	public void setOrder(int order) {
		this.order = order;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13

‍ 在 HiddenHttpMethodFilter 类中,才是真正的处理请求的方法。我们看看其 doFilterInternal 方法:

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
		throws ServletException, IOException {

	HttpServletRequest requestToUse = request;

	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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

该方法做了如下事情:

  • if 判断请求是否 POST 方式(这也就是为什么我们之前说,表单得是 POST 方式),并且请求得是正常的(没有 WebUtils.ERROR_EXCEPTION_ATTRIBUTE 这个属性的值,这个属性是错误信息)

  • 然后获取到 _method 的值(第 8 行)

  • 然后判断 _method 不能是空的,并且统一转为大写(第 9,10 行,因此我们表单里可以忽略大小写)

  • 然后判断允许的请求方式中,是否有前端传过来的请求方式(第 11 行),该集合是这样定义的:也就是兼容 PUT,DELETE,PATCH 请求

    private static final List<String> ALLOWED_METHODS =
    			Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),
    			HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    
    1
    2
    3
  • 下一行(第 12 行)就是关键了:转换请求的方式。这是使用了一种设计模式:包装模式。首先,变量 requestToUse 是原生的 request 对象(第 5 行),然后 HttpMethodRequestWrapper,继承自 HttpServletRequestWrapper 类,而该类又继承自 HttpServletRequest,因此最后返回的也是原生的 Servlet 对象。

    HttpMethodRequestWrapper 源码如下:

private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
	private final String method;
	public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
		super(request);
		this.method = method;
	}

	@Override
	public String getMethod() {
		return this.method;
	}
}
1
2
3
4
5
6
7
8
9
10
11
12

‍ 可以看到,其修改了 method 属性(也就是 HTTP 请求方式),并重写了 getMethod 方法,返回的是传入的值。

最后,过滤器链放行的时候,用是包装过后的 requesWrapper 对象(第 17 行)。以后的方法调用 getMethod 方法时,是调用 requesWrapper 的。 ‍

# 直接发送 REST 请求

当然,上述处理方式主要是处理浏览器表单的,像一些 Rest 使用客户端工具,能直接发送 PUT、DELETE 请求的(例如安卓,PostMan),也就不会被过滤器给拦截。

这也就是为什么,如下配置是选择性开启的:

spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true
1
2
3
4
5

‍ 后续我们主要是作为微服务,只返回数据,不用直接处理表单,因此该项可能用的较少。 ‍

# 改变默认参数

之前我们要求是前端传参 _method,接下来讲讲如何自定义这个名称,例如简单的改为 _m。

首先,自动配置类中,是会判断是否有 HiddenHttpMethodFilter 这个类,没有的话才会注册该过滤器组件(第 4 行的注解,OnMissingBean)

public class WebMvcAutoConfiguration {
    //.............
	@Bean
	@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
	@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", 
                                    name = "enabled", matchIfMissing = false)
	public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
		return new OrderedHiddenHttpMethodFilter();
	}
    //.............
1
2
3
4
5
6
7
8
9
10

‍ 而在过滤器的实现类 HiddenHttpMethodFilter 中,默认是 _method 属性(第 1 行)吗,并且是 final 类型的:

public static final String DEFAULT_METHOD_PARAM = "_method";

private String methodParam = DEFAULT_METHOD_PARAM;

public void setMethodParam(String methodParam) {
	Assert.hasText(methodParam, "'methodParam' must not be empty");
	this.methodParam = methodParam;
}
1
2
3
4
5
6
7
8

不过,后续定义了一个 set 方法,可以让我们修改。

综上,只要我们自己自定义了一个过滤器,然后调用 set 方法修改里面的值,就可以了。我们新建一个类:

package com.peterjxl.boot.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.HiddenHttpMethodFilter;

@Configuration(proxyBeanMethods = false)
public class WebConfig {

    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter filter = new HiddenHttpMethodFilter();
        filter.setMethodParam("hiddenMethod");
        return filter;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

‍ 注意:有两个 HiddenHttpMethodFilter,第二个是响应式的,我们用第一个:

‍

然后修改 index.html:

<form action="/user" method="post">
    <input name="_method" type="hidden" value="DELETE">
    <input name="_m" type="hidden" value="DELETE">
    <input value="REST-DELETE提交" type="submit">
</form>

<form action="/user" method="post">
    <input name="_method" type="hidden" value="PUT">
    <input name="_m" type="hidden" value="PUT">
    <input value="REST-PUT提交" type="submit">
</form>
1
2
3
4
5
6
7
8
9
10
11

‍ 重启,测试,效果是一样的。

# 源码

已将本文源码上传到 Gitee (opens new window) 或 GitHub (opens new window) 的分支 demo11,读者可以通过切换分支来查看本文的示例代码

上次更新: 2025/5/17 12:26:09
静态资源配置原理
请求映射原理

← 静态资源配置原理 请求映射原理→

最近更新
01
吐槽一下《僵尸校园》
05-15
02
2025 年 4 月记
04-30
03
山西大同 “订婚强奸案” 将会给整个社会带来的影响有多严重? - 知乎 转载
04-26
更多文章>
Theme by Vdoing | Copyright © 2022-2025 | 粤 ICP 备 2022067627 号 -1 | 粤公网安备 44011302003646 号 | 点击查看十年之约
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式