Spring MVC 删除功能:用隐藏表单模拟 DELETE 请求的完整解析

Spring MVC 删除功能:用隐藏表单模拟 DELETE 请求的完整解析

原始代码

html 复制代码
<script>
    function del(event){
        event.preventDefault();
        if(confirm("确定要删除吗?")){
            let userform = document.querySelector("#userForm");
            userform.action = event.target.href;
            userform.submit();
        }
    }
</script>

<form id="userForm" method="post">
    <input type="hidden" name="_method" value="delete"/>
</form>

一、这段代码解决什么问题

浏览器中的超链接 <a> 只能发起 GET 请求,HTML 表单原生只支持 GET 和 POST 。而 RESTful 风格的删除操作要求使用 DELETE 方法。

这段代码的做法是:

  1. 点击"删除"链接时,先阻止链接默认的 GET 跳转。
  2. 弹出确认框,用户确认后才继续。
  3. 找到一个隐藏的 POST 表单,把表单的提交地址改成删除链接的地址。
  4. 提交这个 POST 表单,同时携带 _method=delete。
  5. 服务端的 HiddenHttpMethodFilter 读取 _method,把 POST 改写成 DELETE。
  6. Spring MVC 用 @DeleteMapping 匹配并执行删除。

一句话概括:用超链接提供删除地址,用隐藏表单以 POST 方式提交,再用 _method 把 POST 伪装成 DELETE。

二、逐行代码解释

1. 定义删除函数

javascript 复制代码
function del(event){

del 是自定义函数名,绑定在删除链接的 onclick 事件上。参数 event 是浏览器传入的事件对象,包含触发事件的元素、默认行为等信息。

2. 阻止链接默认跳转

javascript 复制代码
event.preventDefault();

超链接 <a href="..."> 被点击时,浏览器默认会发起 GET 请求并跳转页面。preventDefault() 取消这个默认行为,否则页面会先跳走,后面的表单提交逻辑无法执行。

这一行必须放在函数最前面。

3. 弹出确认框

javascript 复制代码
if(confirm("确定要删除吗?")){

confirm() 是浏览器内置的阻塞式确认框,返回布尔值:

  • 点击"确定"返回 true。
  • 点击"取消"返回 false。

只有返回 true 时才进入 if 内部执行删除提交。用户取消时,函数结束,不会发生任何请求。前端确认只是防误触,不能替代后端权限和参数校验。

4. 找到隐藏表单

javascript 复制代码
let userform = document.querySelector("#userForm");

document.querySelector("#userForm") 根据 id 查找页面中的表单元素。

对应的表单是:

html 复制代码
<form id="userForm" method="post">

querySelector 返回第一个匹配的元素。这里要求页面中 id="userForm" 唯一,否则可能选错表单。

5. 修改表单提交地址

javascript 复制代码
userform.action = event.target.href;

event.target 是触发事件的元素,也就是被点击的删除链接。href 是这个链接的目标地址,例如:

复制代码
http://localhost:8080/usermgt/user/1

这一行把隐藏表单的 action 设置为该地址。表单原本没有写 action,提交时会默认提交到当前页面;动态赋值后,提交目标就变成了删除链接指向的 URL。

更稳妥的写法是 event.currentTarget.href:

javascript 复制代码
userform.action = event.currentTarget.href;

区别在于:如果 <a> 内部还有 <span>、<i> 等子元素,用户点到的可能是子元素,event.target 会指向子元素而不是 <a>,子元素没有 href。event.currentTarget 始终指向绑定事件的 <a> 元素。

6. 提交表单

javascript 复制代码
userform.submit();

调用表单的原生 submit() 方法,立即以表单的 method 提交。这里的 method 是 post,所以浏览器发出的是 POST 请求。

注意:form.submit() 不会触发 onsubmit 事件。如果表单上有 onsubmit 校验,这里不会执行。当前表单只有隐藏字段,不需要额外校验。

7. 隐藏的 _method 字段

html 复制代码
<input type="hidden" name="_method" value="delete"/>

这是整个方案的关键。浏览器提交的是 POST,但请求参数里带了一个 _method=delete。

服务端配置 HiddenHttpMethodFilter 后,过滤器会读取这个参数,把请求方法从 POST 改写成 DELETE。Spring MVC 随后按照 DELETE 方法去匹配 @DeleteMapping。

传统 web.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>

Spring Boot 中默认关闭,需要显式开启:

yaml 复制代码
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

三、完整使用示例

删除链接通常写在表格的每一行中:

html 复制代码
<tr th:each="user : ${users}">
    <td th:text="${user.id}"></td>
    <td th:text="${user.username}"></td>
    <td>
        <a th:href="@{/user/{id}(id=${user.id})}" 
           onclick="del(event)">删除</a>
    </td>
</tr>

<form id="userForm" method="post">
    <input type="hidden" name="_method" value="delete"/>
</form>

假设用户 id 是 1,th:href 渲染为:

复制代码
/usermgt/user/1

点击删除后:

  1. preventDefault() 阻止链接跳转。
  2. 确认框弹出。
  3. 表单 action 被设置为 http://localhost:8080/usermgt/user/1。
  4. 表单以 POST 提交,参数包含 _method=delete。
  5. 过滤器把 POST 改写成 DELETE。
  6. Spring MVC 匹配 @DeleteMapping("/user/{id}")。

后端方法示例:

java 复制代码
@DeleteMapping("/user/{id}")
public String deleteUser(@PathVariable Long id) {
    userService.deleteById(id);
    return "redirect:/user";
}

四、执行流程图

复制代码
点击删除链接
  │
  ▼
preventDefault() 阻止 GET 跳转
  │
  ▼
confirm() 弹出确认框
  │
  ├── 取消 → 函数结束,无请求
  │
  └── 确定
        │
        ▼
querySelector("#userForm") 找到隐藏表单
        │
        ▼
form.action = 链接的 href
        │
        ▼
form.submit() 以 POST 提交
        │
        ▼
请求携带 _method=delete
        │
        ▼
HiddenHttpMethodFilter 把 POST 改写为 DELETE
        │
        ▼
DispatcherServlet 匹配 @DeleteMapping("/user/{id}")
        │
        ▼
执行删除,重定向到列表

五、常见问题与注意事项

1. 忘记配置 HiddenHttpMethodFilter

如果过滤器没生效,服务端收到的仍然是 POST,@DeleteMapping 匹配不到,返回 405。日志里会看到:

复制代码
POST "/usermgt/user/1"
HttpRequestMethodNotSupportedException

2. 表单 method 写成 GET

html 复制代码
<form id="userForm" method="get">

这样提交的是 GET,_method 不会按预期生效。必须使用 method="post"。

3. event.target.href 取不到地址

如果删除链接内部有子元素,event.target 可能是子元素。改为:

javascript 复制代码
userform.action = event.currentTarget.href;

4. 表单 id 重复

页面中如果有多个 id="userForm",querySelector 只会返回第一个,可能导致提交到错误的地址。确保 id 唯一。

5. 路径与后端映射不一致

链接 href 生成的路径必须和 @DeleteMapping 完全匹配。例如后端是:

java 复制代码
@DeleteMapping("/user/{id}")

链接就应该是 /user/1。如果链接是 /user,后端却要求 /user/{id},会 405 或 404。

6. 前端确认不是安全机制

confirm() 只是防误触。真正的权限校验、存在性校验、关联数据检查必须在后端完成。

7. Spring Security 下的 CSRF

如果项目启用了 Spring Security,POST 表单需要携带 CSRF token,否则会被拒绝。隐藏表单中需要额外加:

html 复制代码
<input type="hidden" th:name="${_csrf.parameterName}" 
       th:value="${_csrf.token}"/>

六、为什么不用 AJAX 或直接 GET 删除

方案 是否推荐 原因
<a href="/delete?id=1"> GET 删除 不推荐 GET 应该是安全的、幂等的,爬虫和预加载可能误触发删除
隐藏表单 + _method 推荐 不依赖 JavaScript 框架,传统 MVC 项目常用
fetch / axios 发 DELETE 推荐 前后端分离项目更常见,控制更灵活

这段代码属于传统服务端渲染项目的经典做法,适合 Thymeleaf + Spring MVC 的表单场景。

七、记忆方法

1. 四步口诀

复制代码
拦默认,问确认,改地址,交表单。

对应:

复制代码
preventDefault → confirm → action = href → submit

2. 表单三要素

复制代码
id 要唯一,
method 用 post,
_method 写 delete。

3. 链路五步

复制代码
点击链接 → 阻止跳转 → 确认 → 表单改地址 → POST 带 _method
→ 过滤器改写 DELETE → @DeleteMapping 匹配

4. 两个容易记错的地方

复制代码
event.target 可能不是链接,改用 currentTarget 更稳。
form.submit() 不会触发 onsubmit。

5. 一句话总结

链接只负责提供地址,表单负责发 POST,_method 负责把 POST 变成 DELETE,过滤器负责改写,后端 @DeleteMapping 负责接收。

八、总结

代码片段 作用 注意事项
event.preventDefault() 阻止链接默认 GET 跳转 必须放在函数最前
confirm(...) 删除前确认 只是防误触,不是安全机制
querySelector("#userForm") 找到隐藏表单 id 必须唯一
userform.action = href 设置提交地址 用 currentTarget 更稳
userform.submit() 以 POST 提交 不触发 onsubmit
_method=delete 让过滤器把 POST 改写为 DELETE 需要 HiddenHttpMethodFilter
method="post" 表单提交方式 不能写成 GET

这段代码的核心是 "用 POST 模拟 DELETE" 。前端用隐藏表单绕开浏览器对 DELETE 的限制,后端用过滤器还原真实方法,Spring MVC 再用 @DeleteMapping 匹配。记住"拦、问、改、交"四步,以及"id 唯一、method 是 post、_method 是 delete"三要素,这类删除功能就能独立写出来。最容易出错的地方是过滤器没开、表单 method 写成 GET、路径和后端映射不一致。

相关推荐
bu_shuo1 小时前
删除Windows资源管理器左侧【此电脑】的Linux企鹅图标
linux·windows
润滑卫士1 小时前
工业油液在线监测系统架构:传感网络、数据采集与通讯协议设计
运维
红红谈说1 小时前
定时发布关机就停,本地定时任务的补跑策略怎么设计?
运维·服务器·定时任务·本地调度·补跑策略·幂等互斥·执行记录
宁波阿成1 小时前
CentOS 7 环境下 Tomcat 8.5 升级至 9.0.123 操作总结
linux·centos·tomcat
4SAPI2 小时前
企业级大模型工作流基础设施选型指南:4SAPI、硅基流动与 OpenRouter 的架构权衡
运维·人工智能·架构·自动化
2401_868534782 小时前
论纯IPv6(IPv6-Only)演进与地址规划设计与实施
服务器·网络
幽默小吴2 小时前
nginx篇
运维·nginx
爱和冰阔落2 小时前
【Linux网络】UDP 服务器怎么写?从 socket、bind 到 recvfrom/sendto,手写完整回显服务器
linux·运维·服务器·网络
maochongsandai1102 小时前
Centos上运行docker应用命令汇总
linux·docker·centos