一、REST 风格的请求
发送 REST 风格的增删改查请求:
发起图书的增删改查请求;使用 REST 风格的 URL 地址
/book/1 GET:查询 1 号图书
/book/1 DELETE:删除 1 号图书
/book/1 PUT:更新 1 号图书
/book POST:添加图书
http://localhost:8080/index.jsp
具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删
思考:浏览器的 form 表单只提供了 get 与 post 这两种提交方式,如何进行 put 和 delete 方式的提交呢?
为了支持页面发起 PUT 和 DELETE 的请求,Spring 提供了对 REST 风格的支持:
(1)SpringMVC 中有一个 Filter,可以把普通的请求转化为规定形式的请求,配置这个 Filter;
(2)如何发起其他形式请求?
按照以下要求:
① 创建一个 post 类型的表单;
② 表单项中携带一个 _method
的参数;
③ 这个 _method
的值就是 DELETE 或者 PUT;
HiddenHttpMethodFilter
:浏览器 form 表单只支持 GET 与 POST 请求,而 DELETE、PUT 等 method 并不支持,Spring3.0 添加了一个过滤器,可以将这些请求转换为标准的 http 方法,使得支持 GET、POST、PUT 与 DELETE 请求。
二、实验代码
1、控制器代码
@Controller
public class RestTestController {
@RequestMapping(value = "/book/{bid}", method = RequestMethod.GET)
public String getBook(@PathVariable("bid")Integer bid) {
System.out.println("查询到了" + bid + "图书");
return "success";
}
@RequestMapping(value = "/book", method = RequestMethod.POST)
public String saveBook() {
System.out.println("添加了新的图书");
return "success";
}
@RequestMapping(value = "/book/{bid}", method = RequestMethod.DELETE)
public String deleteBook(@PathVariable("bid")Integer bid) {
System.out.println("删除了" + bid + "图书");
return "success";
}
@RequestMapping(value = "/book/{bid}", method = RequestMethod.PUT)
public String updateBook(@PathVariable("bid")Integer bid) {
System.out.println("更新了" + bid + "图书");
return "success";
}
}
2、配置 HiddenHttpMethodFilter 过滤器(SpringMVC 提供的过滤器)
<!-- 支持REST风格的过滤器:可以将POST请求转换为PUT或DELETE请求 -->
<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>
3、页面请求链接
<body>
<a href="book/1">查询图书</a>
<form action="book" method="post">
<input type="submit" value="添加图书">
</form>
<form action="book/1" method="post">
<input name="_method" value="PUT">
<input type="submit" value="更新1号图书">
</form>
<form action="book/1" method="post">
<input name="_method" value="DELETE">
<input type="submit" value="删除1号图书">
</form>
</body>
4、测试
5、可能会出现的问题
(1)这个代码的运行环境需要 Tomcat7.0 以及以下版本。
原因: Tomcat 按照 JCP 规范(JSP2.3 版本)的规定,从 Tomcat8.x 版本开始,不再支持以 HTTP PUT 方式访问 JSP 页面,仅支持 GET、POST 和 HEAD 方式。
而在控制器方法中编写的返回值是一个字符串,SpringMVC 会认为这是一个 jsp 页面,所以报错了。
(2)使用 Tomcat8.0 或以上会报 JSPs only permit GET POST or HEAD 的错误。
解决方式一:在 jsp 页面设置为 isErrorPage
<%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
解决方式二:
如果在 controller 里的 testRESTPUT 的方法加上 @ResponseBody() 注解, 并且返回一个字符串,就不会操作了。
@ResponseBody 注解的作用是将控制器方法的返回值通过适当的转换器转换为指定的格式之后,写入到 Response 对象的 body 区,通常用来返回 JSON 数据或者是 XML 数据。
注意:在使用此注解之后不会再走视图处理器,而是直接将数据写入到输入流中,他的效果等同于通过 Response 对象输出指定格式的数据。
三、HiddenHttpMethodFilter 过滤器源码分析
1、为什么请求隐含参数名称必须叫做 "_method
"?
因为在过滤器中默认是获取参数名为 "_method
" 的参数值的。
2、HiddenHttpMethodFilter 的处理过程
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
//获取表单上 _method 带来的值(delete、put)
String paramValue = request.getParameter(this.methodParam);
//获取表单方式为 POST 而且 _method 有值
if ("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
//把参数转为大写(DELETE、PUT)
String method = paramValue.toUpperCase(Locale.ENGLISH);
//包装了一下request对象,并且重写了request对象的 getMethod
HttpServletRequest wrapper = new HttpMethodRequestWrapper(request, method);
//wrapper.getMethod (PUT 或 DELETE)
filterChain.doFilter(wrapper, response);
}
else {
//直接放行
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
//重写了 getMethod 方法
@Override
public String getMethod() {
return this.method;
}
}