SpringMVC过滤器HiddenHttpMethodFilter内部执行流程
@TOC
基于RESTful风格,我们主要将请求方式分为了四种方式,具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:
-
GET 用来获取资源 -
POST 用来新建资源 -
PUT 用来更新资源 -
DELETE 用来删除资源
大家都知道,form表单的提交方式只有两种:get和post。
而我们现在需要发送四种请求,前两种还好说,因为本来就支持,但是对于put和delete请求。如果直接发送,自然是不可能的。那么如何实现呢?
SpringMVC 提供了 HiddenHttpMethodFilter 帮助我们将 POST 请求转换为 DELETE 或 PUT 请求。
注册配置
因此需要我们在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>
除了添加此配置外,在处理put和delete请求的时候,还需要我们满足以下条件:
- 当前请求方式必须为
post - 当前请求必须传输请求参数
_method
满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 _method 的值,因此请求参数 _method的值才是最终的请求方式。
为什么需要满足这两个条件呢?通过分析源码我们能很清楚的了解到执行流程。
通过源码分析执行流程
首先,既然是过滤器,那么肯定实现了ServletAPI提供给的接口Filter:
他直接继承自GenericFilterBean类,而查看此类我们发现,
他又实现了Filter接口,因此实际上HiddenHttpMethodFilter就是Filter的一个实现类。
知道了这点就好办了,我们知道,所有的过滤器都包含三部分:拦截,处理,放行
而最主要的就是doFilter()方法,也就是我们的处理操作。
对应的HiddenHttpMethodFilter的源码如下:
package org.springframework.web.filter;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS;
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = "_method";
public HiddenHttpMethodFilter() {
}
public void setMethodParam(String methodParam) {
Assert.hasText(methodParam, "'methodParam' must not be empty");
this.methodParam = methodParam;
}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
HttpServletRequest requestToUse = request;
if ("POST".equals(request.getMethod()) && request.getAttribute("javax.servlet.error.exception") == null) {
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
if (ALLOWED_METHODS.contains(method)) {
requestToUse = new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter((ServletRequest)requestToUse, response);
}
static {
ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name()));
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
public String getMethod() {
return this.method;
}
}
}
接下来通过行号分析流程:
-
首先,请求被HiddenHttpMethodFilter拦截,并执行了处理方法doFilterInternal() ,第36行。所进行的处理也就基于此方法。 -
一上来就直接创建了一个新的HTTPServletRequest对象,用于做最后的放行传递,那么肯定是要对这个对象就行加工的,接着往下看 -
38行,获取当前请求方式并判断是否是POST请求,如果是则向下执行,不是直接放行,因此如果我们想要处理put或者delete请求,就必须要求我们的请求方式为post -
接下来就是获取this.methodParam 对应的参数值,那这个this.methodParam 是什么呢?第26行,我们得知为_method ,也就是为什么当前请求必须传输请求参数_method -
判断如果不为空时,则将参数转换为大写,然后判断ALLOWED_METHODS 中是否包含此参数值,通过24行我们知道ALLOWED_METHODS 是一个String类型的List集合,在52行,对他进行了赋值,存放了三个枚举类型: 正好对应着我们需要处理的请求方式,因此我们需要将_method 参数值设置为我们想要转换的请求,即put或者delete -
最后又通过内部包装类HttpMethodRequestWrapper 进行包装,最后放行。
注:
我们在配置注册时,往往也需要配置编码过滤器CharacterEncodingFilter,而如果我们两个都配置时,必须先注册CharacterEncodingFilter,再注册HiddenHttpMethodFilter
原因:
-
在 CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的 -
request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作 -
而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作: -
String paramValue = request.getParameter(this.methodParam);
|