Spring Boot 从“会用”到“精通”:Rest风格原理

REST 风格原理

一、是什么 ------ REST 风格到底是什么?

1. 一句话定义

REST(Representational State Transfer)风格就是使用 HTTP 请求方式动词(GET/POST/PUT/DELETE)来表示对资源的操作,而不是在 URL 中体现行为。

2. 对比:传统风格 vs REST 风格

操作 传统 URL REST 风格 URL HTTP 方法
获取用户 /user/get?id=1 /user/1 GET
新增用户 /user/save /user POST
修改用户 /user/update /user PUT
删除用户 /user/delete?id=1 /user/1 DELETE

REST 风格的核心理念:URL 定位资源,HTTP 方法描述操作

3. 浏览器表单的限制

但这里有一个历史遗留问题:浏览器的原生 HTML <form> 表单只支持 GET 和 POST 两种请求方式,不能直接发送 PUT 和 DELETE 请求。

Spring Boot(底层是 Spring MVC)的做法是使用 HiddenHttpMethodFilter(隐藏方法过滤器) 来"偷梁换柱"。

4. 用法

html 复制代码
<!-- 表单 method 必须是 POST -->
<form method="post" action="/user/1">
    <!-- 隐藏域 _method 指定真正的请求方式 -->
    <input type="hidden" name="_method" value="PUT">
    <button type="submit">修改</button>
</form>

二、为什么 ------ 为什么要这个 Filter?

1. 现实矛盾

  • HTTP 协议层面:PUT、DELETE 是标准方法,框架(Spring MVC)完全支持
  • 浏览器层面 :HTML <form> 只能发 GET 和 POST,不支持 PUT/DELETE
  • AJAX 可以发 PUT/DELETE,但传统表单提交不行

2. Spring 的解决方案

在请求到达 Controller 之前,用一个 Filter(过滤器) 拦截 POST 请求,读取隐藏域 _method 的值,把 POST "伪装" 成 PUT 或 DELETE。

核心思想:装饰器模式(Decorator Pattern) ------ 不修改原始 Request 对象,而是创建一个包装类重写 getMethod() 方法。

3. 版本坑:Spring Boot 2.2.x 之后默认关闭

这曾经是一个被广泛讨论的话题。在 Spring Boot 2.2.x 之前HiddenHttpMethodFilter 是默认自动装配的。但2.2.x 之后,官方将它默认关闭了。

为什么关闭?

现在绝大多数项目都是前后端分离(Vue + Axios、React + Fetch),前端发起的 AJAX 请求可以直接发送真正的 PUT / DELETE HTTP 报文,不再需要借助 HTML 表单伪装。为了节省内存和性能开销,Spring Boot 默认关掉了它。

如何开启(如果需要服务端渲染 + 表单提交的场景)

yaml 复制代码
# 源码位置:springboot2-master/boot-05-web-01/src/main/resources/application.yaml
spring:
  mvc:
    hiddenmethod:
      filter:
        enabled: true

三、怎么做 ------ HiddenHttpMethodFilter 的源码机制

1. 自动装配条件

WebMvcAutoConfiguration 类中:

java 复制代码
@Bean
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled")
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
    return new OrderedHiddenHttpMethodFilter();
}

OrderedHiddenHttpMethodFilter 继承自 HiddenHttpMethodFilter,我们直接看后者的核心逻辑。

2. 核心过滤逻辑 ------ doFilterInternal()

java 复制代码
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
    // 允许"伪装"的方法集合
    private static final List<HttpMethod> ALLOWED_METHODS;
    static {
        ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(
            HttpMethod.PUT.name(),    // "PUT"
            HttpMethod.DELETE.name(), // "DELETE"
            HttpMethod.PATCH.name()   // "PATCH"
        ));
    }
    
    public static final String DEFAULT_METHOD_PARAM = "_method"; // 默认隐藏域名称
    private String methodParam = "_method";

    @Override
    protected void doFilterInternal(HttpServletRequest request, 
                                     HttpServletResponse response, 
                                     FilterChain filterChain) throws ServletException, IOException {
        
        HttpServletRequest requestToUse = request;
        
        // 条件一:必须是 POST 请求
        // 条件二:请求没有异常标记
        if ("POST".equals(request.getMethod()) 
            && request.getAttribute("javax.servlet.error.exception") == null) {
            
            // 获取隐藏域 _method 的值
            String paramValue = request.getParameter(this.methodParam);
            
            if (StringUtils.hasLength(paramValue)) {
                String method = paramValue.toUpperCase(Locale.ENGLISH); // 统一转大写
                
                // 判断是否在允许列表中
                if (ALLOWED_METHODS.contains(method)) {
                    // ★ 核心:用包装类替换原始 request
                    requestToUse = new HttpMethodRequestWrapper(request, method);
                }
            }
        }
        
        // 放行包装后的 request
        filterChain.doFilter(requestToUse, response);
    }
}

3. 装饰器模式 ------ HttpMethodRequestWrapper

java 复制代码
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;  // ★ 重写!返回 _method 的值(PUT/DELETE/PATCH)
    }
}

这是经典的装饰器模式(Decorator Pattern):

  • HttpServletRequestWrapper 是装饰器基类,实现了 HttpServletRequest 接口
  • HttpMethodRequestWrapper 只重写了 getMethod() 这一个方法
  • 其他所有方法(getParameter()getHeader() 等)都原封不动地委托给原始 request

执行效果 :Filter 放行后的整个处理链中,任何组件调用 request.getMethod() 拿到的都是 "PUT" 或 "DELETE",而不再是 "POST"。

4. 完整流程图

复制代码
浏览器表单提交(POST + _method=PUT)
    ↓
Servlet 容器(Tomcat)接收请求
    ↓
HiddenHttpMethodFilter.doFilterInternal()
    ├── 判断:是 POST 请求?且无异常?→ 是
    ├── 获取 _method 的值:"put" → 转大写 → "PUT"
    ├── 判断:"PUT" 在 ALLOWED_METHODS 中?→ 是
    ├── 创建 HttpMethodRequestWrapper(request, "PUT")
    └── filterChain.doFilter(wrapper, response)  // 放行包装后的 request
        ↓
    DispatcherServlet
        ↓
    HandlerMapping 匹配 @PutMapping("/user")
        ↓
    Controller 方法执行

四、怎么自定义 ------ 修改默认的 _method 参数名

1. 原理

如果不想用 _method 这个名字,可以自定义。因为自动装配的条件是 @ConditionalOnMissingBean(HiddenHttpMethodFilter.class),所以只要我们自己注册一个,Spring Boot 就不会再自动装配了。

HiddenHttpMethodFilter 提供了 setMethodParam() 方法:

java 复制代码
public void setMethodParam(String methodParam) {
    Assert.hasText(methodParam, "'methodParam' must not be empty");
    this.methodParam = methodParam;
}

2. 实现

java 复制代码
// 源码位置:springboot2-master/boot-05-web-01/.../config/WebConfig.java
@Configuration(proxyBeanMethods = false)
public class WebConfig {
    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
        methodFilter.setMethodParam("_m");  // 把约定参数从 _method 改成 _m
        return methodFilter;
    }
}

配置后,前端表单需要写成:

html 复制代码
<input type="hidden" name="_m" value="PUT">  <!-- 不再是 _method -->

3. 前端传值大小写问题

源码中这行代码是关键:

java 复制代码
String method = paramValue.toUpperCase(Locale.ENGLISH);

无论前端传的是 putPutPUT,都会被统一转成大写 PUT,所以前端不区分大小写


五、总结

核心点 说明
核心组件 HiddenHttpMethodFilter(OncePerRequestFilter 子类)
约定参数 _method(可通过 setMethodParam() 修改)
设计模式 装饰器模式(HttpMethodRequestWrapper 重写 getMethod()
触发条件 POST 请求 + 无异常 + _method 值在 ALLOWED_METHODS 中
支持方法 PUT、DELETE、PATCH
默认状态 Spring Boot 2.2.x+ 默认关闭 ,需手动 enabled: true
大小写 不区分,源码中 toUpperCase() 统一处理
自定义 注册自己的 HiddenHttpMethodFilter Bean 覆盖默认行为
相关推荐
love_muming1 小时前
数据结构入门:栈与队列详解
java·开发语言·数据结构
Je1lyfish1 小时前
CMU15-445 (2025 Fall/2026 Spring) Project#4 - Concurrency Control
开发语言·数据库·c++·笔记·后端·算法·系统架构
我登哥MVP1 小时前
Spring Boot 从“会用”到“精通”:静态资源原理
java·spring boot·后端·spring·tomcat·maven·intellij-idea
奋斗的袍子0071 小时前
springboot集成国密算法SM2
java·spring boot·算法
caibixyy1 小时前
Springboot + flowable6.8.0
spring boot·flowable6.8.0
JAVA面经实录9171 小时前
SpringBoot 全套完整版学习文档(零基础+实战+面试+源码)
java·spring boot·spring·架构
接着奏乐接着舞1 小时前
springcloud xxl-job
后端·spring·spring cloud
jasnet_u1 小时前
SpringCloud中Feign透传traceId及日志切面配置
java·spring cloud·feign·日志系统
我是一颗柠檬2 小时前
【Redis】Cluster集群Day11(2026年)
数据库·redis·后端·缓存