【“没有什么是一个断点不能解决的”系列】
一、Rest映射及源码解析
1、Rest风格支持(使用 HTTP 请求方式动词来表示对资源的操作)
-
以前:/getUser 获取用户 /deleteUser 删除用户 /editUser 修改用户 /saveUser 保存用户
-
现在:/user GET-获取用户 DELETE-删除用户 PUT-修改用户 POST-保存用户
-
核心 Filter;HiddenHttpMethodFilter。
-
用法:表单 method=post,隐藏域 _method=put。
-
在 WebMvcAutoConfiguration.class 功能自动配置类中我们可以看到已经配了一个 OrderedHiddenHttpMethodFilter,默认我们的 Rest 功能是可以使用的,跟踪进入 OrderedHiddenHttpMethodFilter,发现继承 HiddenHttpMethodFilter,再跟踪进入 HiddenHttpMethodFilter,在 HiddenHttpMethodFilter 中规定,需要我们带一个隐藏的参数项"_method"。
<form action="/user" method="get"> <input value="REST-GET 提交" type="submit"/> </form> <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>
-
再回到 WebMvcAutoConfiguration.class 中的 OrderedHiddenHttpMethodFilter,发现它是与 spring.mvc.hiddenmethod.filter 配置属性值绑定的,并且默认是 false,也就是默认Rest功能是不开启的,因此我们需要设置为 true 才能开启 Rest 功能。
@Bean @ConditionalOnMissingBean({HiddenHttpMethodFilter.class}) @ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter",name = {"enabled"}) public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() { return new OrderedHiddenHttpMethodFilter(); }
-
2、Rest原理(该原理基于表单提交使用Rest的时候)
-
表单提交会带上"_method"参数,_method=DELETE。
-
请求过来会被 HiddenHttpMethodFilter 过滤器拦截,详细关注 doFilterInternal 拦截方法。
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); }
-
首先获取到原生的请求 requestToUse,然后判断原生请求的请求方式是否为 POST,以及判断原生请求的 Attribute 中是否有请求获取异常,判断条件成立(请求获取正常无异常,请求方式为 POST)后才能进入方法执行。
-
调用 request.getParameter(this.methodParam) 获取请求参数值,也就是获取参数"_method"的值 Value(来自于表单);
-
随后就是判断 paramValue 请求参数值是否获取到了,如果获取到了,就统一转为英文大写,因此在表单不区分大小写;
-
再来判断 ALLOWED_METHODS 允许的方法是否包含了我们参数值代表的方法,分别支持 PUT、DELETE、PATCH;
static { ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.PATCH.name())); }
-
原生 Request(POST),包装模式 RequestWrapper 重写了 getMethod() 方法,返回的是传入的值(DELETE);
关键步在于调用 new HiddenHttpMethodFilter.HttpMethodRequestWrapper(request, method),利用包装模式 HttpMethodRequestWrapper,继承了 HttpServletRequestWrapper 类 也实现了 HttpServletRequest 接口,所以也是一个原生的 Request,但是又在 HttpMethodRequestWrapper 中重写了getMethod 方法,并且将要返回的 method 设置成了传进来的新的 method 值(也就是我们上面获取的参数值 DELETE,来自于表单)。
也就是以后方法调用getMethod是调用RequestWrapper 中的getMethod方法,传入的是"_method"的值。
-
过滤器链放行的时候用 wrapper 放入执行。
-
-
3、如果使用客户端工具进行Rest请求访问
如 PostMan 工具直接发送 PUT、DELETE等方式请求,直接在 HTTP 层将请求方式改为了 PUT、DELETE,就不是 POST 了,判断就不会进入,也就不需要进行 Filter,因此也就不需要设置 spring.mvc.hiddenmethod.filter。
4、扩展
@Bean
@ConditionalOnMissingBean({HiddenHttpMethodFilter.class})
@ConditionalOnProperty(prefix = "spring.mvc.hiddenmethod.filter",name = {"enabled"})
public OrderedHiddenHttpMethodFilter hiddenHttpMethodFilter() {
return new OrderedHiddenHttpMethodFilter();
}
@ConditionalOnMissingBean 表示如果容器中没有 HiddenHttpMethodFilter 类型的组件,就会用默认的这个 OrderedHiddenHttpMethodFilter,如果我们不想用 OrderedHiddenHttpMethodFilter 中的一些规则(例如参数名字为"_method",我们想换成别的名字),我们就可以自己重写一个 HiddenHttpMethodFilter 并放入容器。
@Configuration(proxyBeanMethods = false)
public class WebConfig {
@Bean
public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
HiddenHttpMethodFilter methodFilter = new HiddenHttpMethodFilter();
methodFilter.setMethodParam("_m");
return methodFilter;
}
}