用 HiddenHttpMethodFilter 解决浏览器不支持 PUT/DELETE/PATCH 的问题
背景
RESTful API 普及后,后端Controller里满是 @PutMapping、@DeleteMapping、@PatchMapping,写起来很爽。但浏览器只支持 GET 和 POST 两个 HTTP 方法------很多老系统、页面表单、企业内网场景,根本没法发一个真正的 PUT 请求。
改前端代码?后端项目已经上线,改起来牵一发动全身。
Spring 早就想到了这个场景。
HiddenHttpMethodFilter 是什么
HiddenHttpMethodFilter 是 Spring Web 提供的一个 jakarta.servlet.Filter。它的核心逻辑很简单:
- 拦截所有 POST 请求
-
- 读取请求体中的
_method参数
- 读取请求体中的
-
- 如果值是
PUT、DELETE或PATCH,就把请求包装成对应的 HTTP 方法
浏览器侧看到的永远是 POST,但后端HttpServletRequest.getMethod()返回的是真实方法名。
- 如果值是
这就是它的唯一功能------转换方法,不做别的。
工作原理
过滤器内部维护了一个 HttpMethod 列表,只允许三种方法转换:
java
private static final List<String> ALLOWED_METHODS =
List.of("PUT", "DELETE", "PATCH");
```
当请求是 POST 且包含 `_method` 参数时,值不在这个列表里就直接忽略,维持 POST 不变。
转换发生在 `HttpServletRequestWrapper` 层------原始请求对象本身没变,是包装类改了 `getMethod()` 的返回值:
```java
// 伪代码逻辑
if ("POST".equals(request.getMethod()) && methodParam != null) {
String realMethod = request.getParameter(methodParam); // 默认取 _method
if (ALLOWED_METHODS.contains(realMethod)) {
return new HttpMethodRequestWrapper(request, realMethod);
}
}
```
这样后续的 Spring 路由匹配(`@PutMapping` 等注解)能正常工作,因为它们看到的就是真实的 HTTP 方法名。
## 配置方法
### Spring Boot 3.x
Spring Boot 3 使用的是 Jakarta EE,Filter 从 `javax.servlet` 换到了 `jakarta.servlet`,其余一样:
```java
@Configuration
public class WebConfig {
@Bean
public FilterRegistrationBean<HiddenHttpMethodFilter> hiddenHttpMethodFilter() {
FilterRegistrationBean<HiddenHttpMethodFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new HiddenHttpMethodFilter());
// 可选:改参数名,默认是 _method
// bean.addUrlPatterns("/*");
return bean;
}
}
```
如果用 Spring Boot 自动配置,什么都不用配,Filter 默认已经启用。
### Spring MVC XML 配置(旧项目)
```xml
<filter>
<filter-name>hiddenHttpMethodFilter</filter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>hiddenHttpMethodFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
```
### 前端表单配合
```html
<!-- 发送 PUT 请求 -->
<form action="/users/1" method="POST">
<input type="hidden" name="_method" value="PUT" />
<input type="text" name="name" value="张三" />
<button type="submit">提交</button>
</form>
<!-- 发送 DELETE 请求 -->
<form action="/users/1" method="POST">
<input type="hidden" name="_method" value="DELETE" />
<button type="submit">删除</button>
</form>
```
AJAX 场景也一样:
```javascript
// 发送 PUT 请求
fetch('/users/1', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: '_method=PUT&name=张三'
});
// 发送 DELETE 请求
fetch('/users/1', {
method: 'POST',
body: '_method=DELETE'
});
```
## 一个容易踩的坑:Multipart 和 HiddenHttpMethodFilter 的顺序
官方文档里有这么一句:
> NOTE: This filter needs to run after multipart processing in case of a multipart POST request, due to its inherent need for checking a POST body parameter.
意思是:如果 POST 请求是 `multipart/form-data`(文件上传场景),Filter 必须在 `MultipartFilter` 之后执行。因为文件上传的请求体被解析后,`_method` 参数才能从 body 中读到。
Spring Boot 中 Filter 的顺序通过 `FilterRegistrationBean` 的 `setOrder()` 控制。数字越小越靠前:
```java
@Configuration
public class FilterOrderConfig {
@Bean
public FilterRegistrationBean<MultipartFilter> multipartFilter() {
FilterRegistrationBean<MultipartFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new MultipartFilter());
bean.setOrder(Ordered.HIGHEST_PRECEDENCE); // 最先
return bean;
}
@Bean
public FilterRegistrationBean<HiddenHttpMethodFilter> hiddenHttpMethodFilter() {
FilterRegistrationBean<HiddenHttpMethodFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new HiddenHttpMethodFilter());
bean.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); // MultipartFilter 之后
return bean;
}
}
```
如果顺序反了------`HiddenHttpMethodFilter` 先执行,`_method` 参数还没被解析出来,转换就不生效。
## 支持的方法只有三种
`HiddenHttpMethodFilter` 只支持 `PUT`、`DELETE`、`PATCH`,不支持 `GET`(浏览器本来就能发),也不支持 `OPTIONS` 和 `HEAD`。如果要支持其他方法,需要自己写 Filter 扩展。
## 实际使用场景
这个 Filter 在以下场景特别有用:
- **企业内部老系统**:页面表单对接 RESTful 后端,不需要改后端代码
- - **第三方平台回调**:很多第三方只支持 POST 回调,但业务需要区分 PUT/DELETE
- - **遗留系统改造**:从 SOAP 迁移到 RESTFUL,不动前端的情况下复用现有 POST 表单
## 总结
`HiddenHttpMethodFilter` 是 Spring 生态里一个"润物细无声"的组件。功能单一、配置简单、几乎零成本解决浏览器 HTTP 方法限制的问题。
核心记住两点:
1. **浏览器发 POST + `_method` 参数**,后端拿到的是真实方法
2. 2. **文件上传场景注意 Filter 顺序**,`MultipartFilter` 要在前
大多数 Spring Boot 项目开箱即用,不用配。只有在需要调整参数名(`methodParam`)或处理 multipart 顺序时才需要手动注册。