一、Rest 简介

从下面的 URL 风格可以看出,我们针对用户的不同操作,URL 都是相同的,我们只是通过 HTTP 的请求方式来确定对 user 进行 增、删、改、查.

URL

HTTP 请求方式

具体操作

/user/1

POST

添加 id 为 1 的用户记录

/user/1

DELETE

删除 id 为 1 的用户记录

/user/1

PUT

修改 id 为 1 的用户记录

/user/1

GET

查询 id 为 1 的用户记录

 

 

 

 

 

 

 

二、Rest 配置原理

Springboot 2.x 对于 Rest 风格的配置都是由 WebMvcAutoConfiguration 这个类完成的,我们这里选取 Springboot-2.3.7.RELEASE 这个版本来探究一下 Springboot 底层对于 Rest 是如何配置的,首先找到 WebMvcAutoConfiguration 这个类,该类中存在一个组件 OrderedHiddenHttpMethodFilter

代码块一、OrderedHiddenHttpMethodFilter

// 1、下面的条件成立的情况下,往 IOC 容器中注入该组件
@Bean
// 2、如果当前环境下不存在 HiddenHttpMethodFilter 这个类
@ConditionalOnMissingBean(HiddenHttpMethodFilter.class)
// 3、如果 application.properties 中没有配置 spring.mvc.hiddenmethod.filter.enabled=true ,那么该判断条件不成立
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter", name = "enabled", matchIfMissing = false)
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
	return new OrderedHiddenHttpMethodFilter();
}

点开 OrderedHiddenHttpMethodFilter 这个过滤器

代码块二、OrderedHiddenHttpMethodFilter 继承 HiddenHttpMethodFilter

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

    public OrderedHiddenHttpMethodFilter() {
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }
}

点开 HiddenHttpMethodFilter 这个过滤器

代码块三、HiddenHttpMethodFilter 

public class HiddenHttpMethodFilter extends OncePerRequestFilter {
	// 1、允许的请求方式,下面的静态代码块初始化的时候就赋值了 PUT、DELETE、PATCH
    private static final List<String> ALLOWED_METHODS;
	// 2、默认的请求方式参数 _method
    public static final String DEFAULT_METHOD_PARAM = "_method";
	// 3、成员变量初始赋值为 _method ,可以通过 setMethodParam 改变该值
    private String methodParam = "_method";

    public HiddenHttpMethodFilter() {
    }
	
    public void setMethodParam(String methodParam) {
        Assert.hasText(methodParam, "'methodParam' must not be empty");
        this.methodParam = methodParam;
    }
	// 4、过滤器 doFilter 方法
    protected void doFilterInternal(HttpServletRequest request, 
	HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
		// 4.1、将原生的 request 赋值给 requestToUse
        HttpServletRequest requestToUse = request;
		// 4.2、如果请求方式为 POST 并且没有发生异常,则判断条件成立 (form 表单提交方式由于不能发送 PUT、DELETE 方式需要借助包装模式转换)
        if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
			// 4.3、获取页面 form 表单中 key = _method 所携带的值 value (this.method 的初始值为 _method )
            String paramValue = request.getParameter(this.methodParam);
			// 4.4、如果参数值存在
            if (StringUtils.hasLength(paramValue)) {
				// 4.5 将该参数值转为大写
                String method = paramValue.toUpperCase(Locale.ENGLISH);
				// 4.6、ALLOWED_METHODS 有三种,分别是 PUT、DELETE、PATCH
                if (ALLOWED_METHODS.contains(method)) {
					// 4.7、使用包装模式,将 form 表单传入的请求方式替换原生请求的 POST 方式
                    requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
                }
            }
        }
		// 8、放行将包装过的 requestToUse (如果该配置类生效了,使用类似 postman 客户端的时候会直接放行)
        filterChain.doFilter((ServletRequest)requestToUse, response);
    }

	// 5、静态代码块初始化允许的请求方式,总共有三种,分别是 PUT、DELETE、PATCH
    static {
        ALLOWED_METHODS = Collections.unmodifiableList(
		Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
    }

	// 6、静态内部类 HttpMethodRequestWrapper ,它继承 HttpServletRequestWrapper
	// HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest
    private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
        private final String method;
		// 7、静态内部类构造方法
        public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
            super(request);
            this.method = method;
        }

        public String getMethod() {
            return this.method;
        }
    }
}

 

三、总结

3.1、Form 表单提交方式

传统的 Form 表单的提交方式,只支持 GET、POST 两种提交方式,无法发送 DELETE、PUT 等请求方式,如果你想要支持 DELETE、PUT 这种提交方式,就必须借助于 HiddenHttpMethodFilter 这个过滤器来将 POST 请求方式转为 PUT、DELETE(Springboot 默认配置的是 OrderedHiddenHttpMethodFilter),所以我们需要向 IOC 容器中注入该组件,这样请求发出后,会被该过滤器拦截,过滤器经过相应的处理之后,将请求放行

对于 Form 表单这种提交方式,如果要往 IOC 容器中注入 OrderedHiddenHttpMethodFilter 这个过滤器,那么我们就必须要满足它的判断条件

3.1.1、容器中不能存在 HiddenHttpMethodFilter 类型的过滤器,否则判断条件就失效了(代码块一中的判断条件)

3.1.2、application.properties 中必须配置 spring.mvc.hiddenmethod.filter.enabled=true ,否则判断条件就会失效(代码块一中的判断条件)

# 如果是 Form 表单的提交方式,必须要开启此配置才能支持 Rest ,而对于 postman 等客户端,则不需要此配置
spring.mvc.hiddenmethod.filter.enabled=true

3.1.3、页面表单中必须携带一个请求参数 _method ,并且它的请求方式必须要为 POST(代码块三中的过滤器 doFilter 方法)

// 请求方式必须为 POST 
<form action="/user" method="post">
	// 必须携带请求参数 _method
    <input name="_method" type="hidden" value="delete"/>
    <input value="REST-DELETE 提交" type="submit"/>
</form>

 

3.2、其它客户端

例如 postman ,它是可以直接 PUT、PATCH、DELETE 等请求方式,不需要借助于 OrderedHiddenHttpMethodFilter ,所以客户端的方式无需任何配置

springboot 引入dubbo后 api接口pending_REST

 

四、实践

4.1、Form 表单发送 PUT、DELETE 请求方式

4.1.1、index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8"/>
    <title>首页</title>
    <link rel="icon" th:href="@{/public/favicon.ico}" type="image/x-icon"/>
    <link rel="bookmark" th:href="@{/public/favicon.ico}" type="image/x-icon"/>
</head>
<body>

<h1>welcome to index page!!!</h1>
测试REST风格;
<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>

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

</body>
</html>

4.1.2、RestReqController

@RestController
public class RestReqController {
    // 可以使用 @PostMapping("/user") 替换
    @RequestMapping(value = "/user", method = RequestMethod.POST)
    public String saveUser() {
        return "POST-张三";
    }

    // 可以使用 @DeleteMapping("/user") 替换
    @RequestMapping(value = "/user", method = RequestMethod.DELETE)
    public String deleteUser() {
        return "DELETE-张三";
    }

    // 可以使用 @PutMapping("/user") 替换
    @RequestMapping(value = "/user", method = RequestMethod.PUT)
    public String putUser() {
        return "PUT-张三";
    }

    // 可以使用 @GetMapping("/user") 替换
    @RequestMapping(value = "/user", method = RequestMethod.GET)
    public String getUser() {
        return "GET-张三";
    }
}

 

4.2、扩展

如何定义 _method 的名称

经过上面的代码我们分析如下,当我们满足了判断条件,启用了 Springboot 默认的配置,即向容器中注入了一个 OrderedHiddenHttpMethodFilter 过滤器,该过滤器继承自 HiddenHttpMethodFilter ,我们实现请求方式转换实际上用的是 HiddenHttpMethodFilter 这个过滤器的功能,那么我们完全可以自己向 IOC 容器中注入 HiddenHttpMethodFilter 这个组件,然后调用它的 setMethodParam 方法来改变 methodParam 的默认值,这样我们就可以使用自己喜欢的名称了

4.2.1、自定义配置类向容器中注入 HiddenHttpMethodFilter 组件

@Configuration(proxyBeanMethods = false)
public class MyWebMvcConfig {

    @Bean
    public HiddenHttpMethodFilter myMethodFilter(){
        HiddenHttpMethodFilter hiddenHttpMethodFilter = new HiddenHttpMethodFilter();
        hiddenHttpMethodFilter.setMethodParam("_xiaomaomao");
        return hiddenHttpMethodFilter;
    }
}

4.2.2、Form 表单请求参数名称改为 _xiaomaomao

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