【SpringMVC从入门到精通】05-RESTful
RESTful
1、RESTful 简介
REST:Representational State Transfer,表现层资源状态转移
- 表现层/表示层:前端的视图页面到后端的控制层即为“表现层”
- 资源:Web 工程部署到服务器上后,当前 Web 工程中的内容在服务器上都叫“资源”(万物皆资源)
- 状态:资源的表现形式,例如,HTML/JSP 页面、CSS/JS 文件、图片/音频/视频等皆为资源的“状态”
- 转移:浏览器发送请求到服务器,服务端就将请求的资源“转移”到客户端
RESTful:基于REST构建的 API 就是RESTful风格
1.1、资源
- 资源是一种看待服务器的方式,即将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念
- 因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,也可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解
- 与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是“名词”。一个资源可以由一个或多个
URI来标识。URI既是资源的名称,也是资源在 Web 上的地址。对某个资源感兴趣的客户端应用,可以通过资源的URI与其进行交互
URI(Uniform Resource Identifier):统一资源标志符
URL(Uniform Resource Locator):统一资源定位符
URI是一个抽象的、高层次的概念,而URL是具体的方式。简单来说,URL是一种URI
1.2、资源的表述
- 资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端-服务器端之间转移(交换)
- 资源的表述可以有多种格式,例如
HTML/XML/JSON/纯文本/图片/视频/音频等等 - 资源的表述格式可以通过协商机制来确定。请求-响应方向的表述通常使用不同的格式
1.3、状态转移
- 状态转移说的是:在客户端和服务器端之间转移(
transfer)代表资源状态的表述 - 通过转移和操作资源的表述,来间接实现操作资源的目的
2、RESTful 实现
RESTful的实现,具体说就是:HTTP 协议里面,四个表示操作方式的动词GET、POST、PUT、DELETE
它们分别对应四种基本操作:
GET用来获取资源POST用来新建资源PUT用来更新资源DELETE用来删除资源
REST风格URL地址不使用问号键值对方式携带请求参数,而是:
提倡使用统一的风格设计,从前到后各个单词使用斜杠分开,将要发送给服务器的数据作为URL地址的一部分,以保证整体风格的一致性
以往,我们访问资源的方式五花八问。例如,
- 获取用户信息通过
getUserById/selectUserById/findUserById/等- 删除用户信息通过
deleteUserById/removeUserById等- 更新用户信息通过
updateUser/modifyUser/saveUser等- 新增用户信息通过
addUser/createUser/insertUser等上述操作的资源都是用户信息。按照
RESTful思想,既然操作的资源一样,那么请求路径就应该一样
用一张表格来对比传统方式和REST风格对资源操作的区别
| 操作 | 传统方式 | REST 风格 |
|---|---|---|
| 查询 | getUserById?id=1 |
user/1-->get请求 |
| 保存 | saveUser |
user-->post请求 |
| 删除 | deleteUserById?id=1 |
user/1-->delete请求 |
| 更新 | updateUser |
user-->put请求 |
3、使用 RESTful 模拟操作用户资源
需求分析:使用 RESTful 模拟用户资源的增删改查,通过路径中的占位符传递请求参数,通过不同的请求方式对应资源的不同操作
| RESTful 路径 | 请求方式 | 操作 |
|---|---|---|
/user |
GET |
查询所有用户信息 |
/user/1 |
GET |
根据用户ID查询用户信息 |
/user |
POST |
添加用户信息 |
/user/1 |
DELETE |
根据用户ID删除用户信息 |
/user |
PUT |
修改用户信息 |
3.1、用户的查询、添加
后台代码实现
RESTfulController.java
@Controller
@RequestMapping("restfulcontroller")
public class RESTfulController {
@RequestMapping(value = "/user", method = RequestMethod.GET)
public String getAllUser() {
System.out.println("查询所有用户信息");
return "success";
}
@RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
public String getUserById(@PathVariable("id") String id) {
System.out.println("根据用户ID查询用户信息:" + id);
return "success";
}
@RequestMapping(value = "/user", method = RequestMethod.POST)
public String insertUser(User user) { // User 沿用之前的对象
System.out.println("添加用户信息:" + user);
return "success";
}
/** 注:由于 PUT、DELETE 请求比较特殊,后面再做补充 */
}
前台测试代码
restful.html
<a th:href="@{/restfulcontroller/user}">查询所有用户信息</a><br/>
<a th:href="@{/restfulcontroller/user/1}">根据用户ID查询用户信息</a><br/>
<form th:action="@{/restfulcontroller/user}" method="post">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
性别:<input type="radio" name="gender" value="male">男<input type="radio" name="gender" value="female">女<br/>
年龄:<input type="number" name="age"><br/>
邮箱:<input type="text" name="email"><br/>
<input type="submit" value="添加用户">
</form>
为了能够访问到restful.html前台页面资源,可以通过在控制器中定义一个控制器方法来返回其视图,也可以通过在04-SpringMVC 视图中提到的view-controller视图控制器代替。因为这里只是为了实现页面跳转,没有其他请求过程的处理,所以可以通过在 SpringMVC 配置文件中使用<view-controller>标签进行设置
<mvc:view-controller path="/restful" view-name="restful"></mvc:view-controller>
测试结果

后台日志信息
查询所有用户信息
根据用户ID查询用户信息:1
添加用户信息:User{username='admin', password='11111', gender='male', age='18', email='123@qq.com'}
3.2、用户的修改、删除
在01-@RequestMapping 注解中,我们提到过
form表单默认只支持GET和POST请求,如果直接通过method属性指定为PUT和DELETE,会默认以GET请求方式处理而想要实现
PUT和DELETE请求,需要在web.xml中配置 SpringMVC 提供的HiddenHttpMethodFilter过滤器,并在前台页面使用隐藏域来设置PUT和DELETE类型的请求方式当然,也可以使用
AJAX发送PUT和DELETE请求,但是需要注意PUT和DELETE仅部分浏览器支持
为了更清楚地了解HiddenHttpMethodFilter过滤器到底干了什么,这里对其源码进行剖析
public class HiddenHttpMethodFilter extends OncePerRequestFilter {
private static final List<String> ALLOWED_METHODS = Collections.unmodifiableList(Arrays.asList(HttpMethod.PUT.name(),HttpMethod.DELETE.name(),HttpMethod.PATCH.name()));
public static final String DEFAULT_METHOD_PARAM = "_method";
private String methodParam = DEFAULT_METHOD_PARAM;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
HttpServletRequest requestToUse = request;
// 要求原请求方式为 POST 请求
if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) {
// methodParam值:_method
// paramValue值:_method属性值
String paramValue = request.getParameter(this.methodParam);
if (StringUtils.hasLength(paramValue)) {
// _method属性值转为全大写形式,put==>PUT,delete==>DELETE
String method = paramValue.toUpperCase(Locale.ENGLISH);
// 判断_method属性值是否为{"PUT","DELETE","PATCH"}中的一个
if (ALLOWED_METHODS.contains(method)) {
// “偷梁换柱”,包装为一个新的请求对象
requestToUse = new HttpMethodRequestWrapper(request, method);
}
}
}
filterChain.doFilter(requestToUse, response);
}
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;
}
}
}
“源码在手,天下我有”。接下来,将理论付诸实践
配置文件
web.xml配置HiddenHttpMethodFilter过滤器
<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>
后台代码实现
@RequestMapping(value = "/user", method = RequestMethod.PUT)
public String updateUser(User user) {
System.out.println("修改用户信息:" + user);
return "success";
}
// 这里的写法格式没有问题,但业务逻辑其实大有问题,下面再详细说
@RequestMapping(value = "/user", method = RequestMethod.DELETE)
public String deleteUser(User user) {
System.out.println("删除用户信息:" + user);
return "success";
}
前台测试代码
在添加用户的表单基础上,添加隐藏域<input type="hidden" name="_method" value="put">
<form th:action="@{/restfulcontroller/user}" method="post">
<input type="hidden" name="_method" value="put">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
性别:<input type="radio" name="gender" value="male">男<input type="radio" name="gender" value="female">女<br/>
年龄:<input type="number" name="age"><br/>
邮箱:<input type="text" name="email"><br/>
<input type="submit" value="修改用户">
</form>
<hr/>
<!-- 这里的写法格式没有问题,但业务逻辑其实大有问题,下面再详细说 -->
<form th:action="@{/restfulcontroller/user}" method="post">
<input type="hidden" name="_method" value="delete">
用户名:<input type="text" name="username"><br/>
密码:<input type="password" name="password"><br/>
性别:<input type="radio" name="gender" value="male">男<input type="radio" name="gender" value="female">女<br/>
年龄:<input type="number" name="age"><br/>
邮箱:<input type="text" name="email"><br/>
<input type="submit" value="删除用户">
</form>
测试结果
修改用户

删除用户

后台日志信息
修改用户信息:User{username='user', password='11111', gender='female', age='1', email='111@qq.com'}
删除用户信息:User{username='user', password='11111', gender='female', age='1', email='111@qq.com'}
到这里,应该需要指明的是我们在设计RESTful实现时,对删除用户信息的要求下面这样的
| RESTful 路径 | 请求方式 | 操作 |
|---|---|---|
/user/1 |
DELETE |
根据用户ID删除用户信息 |
换句话说,这里应该通过超链接而非表单形式,即通过用户ID来对用户信息进行删除操作。而一般情况下,我们是通过行编辑删除某一行数据,或是通过选中表单的数据来进行批量删除,这些功能在详细案例时会详细介绍
4、CharacterEncodingFilter 和 HiddenHttpMethodFilter 的配置顺序
目前,我们在web.xml配置文件中对CharacterEncodingFilter和HiddenHttpMethodFilter的配置顺序如下
<!--处理编码-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 略 -->
<!--配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST 请求转为 DELETE 或 POST 请求 -->
<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>
如果,将两者顺序颠倒互换,即
<!--配置 org.springframework.web.filter.HiddenHttpMethodFilter: 可以把 POST 请求转为 DELETE 或 POST 请求 -->
<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>
<!-- 略 -->
<!--处理编码-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceResponseEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
看看顺序改变之后,对请求的编码会有什么影响

后台日志信息
修改用户信息:User{username='å¼ ä¸', password='123456', gender='male', age='18', email='123@qq.com'}
可以发现,中文的“张三”乱码了,这是为什么呢?
在02-SpringMVC 获取请求参数一节中的7、处理乱码问题中,我们尝试在获取请求参数之前,通过
request.setCharacterEncoding("UTF-8");来设置请求编码格式,但是没有生效。分析的原因是“请求参数获取在前,设置编码格式在后”导致的。我们也提出了 2 种解决方案:
- 1、获取请求参数之后,手动解码编码。但是这种显然不合理,所以直接 pass
- 2、获取请求参数之前“做手脚”。这种方式就是 SpringMVC 中提供的
CharacterEncodingFilter过滤器,来对请求编码做统一处理现在的问题就是:在
CharacterEncodingFilter之前配置了HiddenHttpMethodFilter导致了失效所以我们需要搞清楚,为什么
CharacterEncodingFilter的配置顺序会影响到编码的效果?或者说为什么HiddenHttpMethodFilter会使之失效?
通过上面对HiddenHttpMethodFilter源码的剖析,它会获得_method这个请求参数,这就导致执行到CharacterEncodingFilter过滤器时,已经是获取请求参数之后了,所以会导致上述中文乱码问题
因此,我们必须要将CharacterEncodingFilter过滤器尽量配置在其他过滤器之前。这样就能保证在任何过滤器获取请求之前,获得的失已经处理过编码格式的请求参数了
我们再将CharacterEncodingFilter和HiddenHttpMethodFilter的配置顺序还原至之前的状态,即CharacterEncodingFilter在前而HiddenHttpMethodFilter在后的情况,进行测试再查看后台日志信息
修改用户信息:User{username='张三', password='', gender='male', age='18', email='123@qq.com'}
这时,当请求参数中包含中文时,就不会出现乱码的情况了
总结
本节重点掌握内容
- 明确
REST和RESTful的关系,明确表现层、资源、状态、转移这几个概念的含义REST,表现层资源状态转移RESTful,基于REST构建的 API 就是RESTful风格
- 明确
RESTful的实现,是通过不同的请求方式来对应资源的不同操作,通过路径中的占位符传递请求参数 - 熟练掌握如何通过
RESTful进行资源的增删改查操作,以及如何处理PUT和DELETE这两种特殊的请求方式 - 明确
CharacterEncodingFilter和HiddenHttpMethodFilter的配置顺序,明白两个过滤器的源码处理逻辑
附上导图,仅供参考


浙公网安备 33010602011771号