Spring MVC 删除功能:用隐藏表单模拟 DELETE 请求的完整解析
原始代码
<script>functiondel(event){event.preventDefault();if(confirm("确定要删除吗?")){letuserform=document.querySelector("#userForm");userform.action=event.target.href;userform.submit();}}</script><formid="userForm"method="post"><inputtype="hidden"name="_method"value="delete"/></form>一、这段代码解决什么问题
浏览器中的超链接<a>只能发起GET请求,HTML 表单原生只支持GET和POST。而 RESTful 风格的删除操作要求使用DELETE方法。
这段代码的做法是:
- 点击“删除”链接时,先阻止链接默认的 GET 跳转。
- 弹出确认框,用户确认后才继续。
- 找到一个隐藏的 POST 表单,把表单的提交地址改成删除链接的地址。
- 提交这个 POST 表单,同时携带
_method=delete。 - 服务端的
HiddenHttpMethodFilter读取_method,把 POST 改写成 DELETE。 - Spring MVC 用
@DeleteMapping匹配并执行删除。
一句话概括:用超链接提供删除地址,用隐藏表单以 POST 方式提交,再用_method把 POST 伪装成 DELETE。
二、逐行代码解释
1. 定义删除函数
functiondel(event){del是自定义函数名,绑定在删除链接的onclick事件上。参数event是浏览器传入的事件对象,包含触发事件的元素、默认行为等信息。
2. 阻止链接默认跳转
event.preventDefault();超链接<a href="...">被点击时,浏览器默认会发起 GET 请求并跳转页面。preventDefault()取消这个默认行为,否则页面会先跳走,后面的表单提交逻辑无法执行。
这一行必须放在函数最前面。
3. 弹出确认框
if(confirm("确定要删除吗?")){confirm()是浏览器内置的阻塞式确认框,返回布尔值:
- 点击“确定”返回
true。 - 点击“取消”返回
false。
只有返回true时才进入 if 内部执行删除提交。用户取消时,函数结束,不会发生任何请求。前端确认只是防误触,不能替代后端权限和参数校验。
4. 找到隐藏表单
letuserform=document.querySelector("#userForm");document.querySelector("#userForm")根据 id 查找页面中的表单元素。
对应的表单是:
<formid="userForm"method="post">querySelector返回第一个匹配的元素。这里要求页面中id="userForm"唯一,否则可能选错表单。
5. 修改表单提交地址
userform.action=event.target.href;event.target是触发事件的元素,也就是被点击的删除链接。href是这个链接的目标地址,例如:
http://localhost:8080/usermgt/user/1这一行把隐藏表单的action设置为该地址。表单原本没有写action,提交时会默认提交到当前页面;动态赋值后,提交目标就变成了删除链接指向的 URL。
更稳妥的写法是event.currentTarget.href:
userform.action=event.currentTarget.href;区别在于:如果<a>内部还有<span>、<i>等子元素,用户点到的可能是子元素,event.target会指向子元素而不是<a>,子元素没有href。event.currentTarget始终指向绑定事件的<a>元素。
6. 提交表单
userform.submit();调用表单的原生submit()方法,立即以表单的method提交。这里的method是post,所以浏览器发出的是 POST 请求。
注意:form.submit()不会触发onsubmit事件。如果表单上有onsubmit校验,这里不会执行。当前表单只有隐藏字段,不需要额外校验。
7. 隐藏的_method字段
<inputtype="hidden"name="_method"value="delete"/>这是整个方案的关键。浏览器提交的是 POST,但请求参数里带了一个_method=delete。
服务端配置HiddenHttpMethodFilter后,过滤器会读取这个参数,把请求方法从 POST 改写成 DELETE。Spring MVC 随后按照 DELETE 方法去匹配@DeleteMapping。
传统web.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 中默认关闭,需要显式开启:
spring:mvc:hiddenmethod:filter:enabled:true三、完整使用示例
删除链接通常写在表格的每一行中:
<trth:each="user : ${users}"><tdth:text="${user.id}"></td><tdth:text="${user.username}"></td><td><ath:href="@{/user/{id}(id=${user.id})}"onclick="del(event)">删除</a></td></tr><formid="userForm"method="post"><inputtype="hidden"name="_method"value="delete"/></form>假设用户 id 是 1,th:href渲染为:
/usermgt/user/1点击删除后:
preventDefault()阻止链接跳转。- 确认框弹出。
- 表单
action被设置为http://localhost:8080/usermgt/user/1。 - 表单以 POST 提交,参数包含
_method=delete。 - 过滤器把 POST 改写成 DELETE。
- Spring MVC 匹配
@DeleteMapping("/user/{id}")。
后端方法示例:
@DeleteMapping("/user/{id}")publicStringdeleteUser(@PathVariableLongid){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" HttpRequestMethodNotSupportedException2. 表单method写成 GET
<formid="userForm"method="get">这样提交的是 GET,_method不会按预期生效。必须使用method="post"。
3.event.target.href取不到地址
如果删除链接内部有子元素,event.target可能是子元素。改为:
userform.action=event.currentTarget.href;4. 表单 id 重复
页面中如果有多个id="userForm",querySelector只会返回第一个,可能导致提交到错误的地址。确保 id 唯一。
5. 路径与后端映射不一致
链接href生成的路径必须和@DeleteMapping完全匹配。例如后端是:
@DeleteMapping("/user/{id}")链接就应该是/user/1。如果链接是/user,后端却要求/user/{id},会 405 或 404。
6. 前端确认不是安全机制
confirm()只是防误触。真正的权限校验、存在性校验、关联数据检查必须在后端完成。
7. Spring Security 下的 CSRF
如果项目启用了 Spring Security,POST 表单需要携带 CSRF token,否则会被拒绝。隐藏表单中需要额外加:
<inputtype="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 → submit2. 表单三要素
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、路径和后端映射不一致。