第六篇:RESTful

第六篇:RESTful

官方文档

简介

REST:Representational State Transfer,表现层资源状态转移。(HTML 页面通过 HTTP 协议,在浏览器和服务器之间传递)

RESTful 是一种风格,可以用可以不用。现在这是一种主流的风格。很多网站都使用了,比如百度百科

资源

资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念。因为资源是一个抽象的概念,所以它不仅仅能代表服务器文件系统中的一个文件、数据库中的一张表等等具体的东西,可以将资源设计的要多抽象有多抽象,只要想象力允许而且客户端应用开发者能够理解。与面向对象设计类似,资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个 URI 来标识。URI 既是资源的名称,也是资源在 Web 上的地址。对某个资源感兴趣的客户端应用,可以通过资源的 URI 与其进行交互。

服务器上万物皆为资源,就跟 Java 里万物皆为对象一样。

访问资源的时候使用的 URL 是固定的,或者说前缀是固定的(后面跟着不同的参数),这样会让整体风格保持一致性,同时非常的直观。

资源的表述

资源的表述是一段对于资源在某个特定时刻的状态的描述。可以在客户端 - 服务器端之间转移(交换)。资源的表述可以有多种格式,例如 HTML/XML/JSON/纯文本/图片/视频/音频等等。资源的表述格式可以通过协商机制来确定。请求 - 响应方向的表述通常使用不同的格式。

其实说白了,资源的表述就是资源的格式

状态转移

状态转移说的是:在客户端和服务器端之间转移(transfer)代表资源状态的表述。通过转移和操作资源的表述,来间接实现操作资源的目的。

转移就是通信,现在普通使用 HTTP 协议通信,操作,对应的就是不同的 HTTP 方法

RESTful 在 HTTP 协议中的实现

具体说,就是 HTTP 协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。

它们分别对应四种基本操作:GET 用来获取资源,POST 用来新建资源,PUT 用来更新资源,DELETE 用来删除资源。

REST 风格提倡 URL 地址使用统一的风格设计,从前到后各个单词使用斜杠分开,不使用问号键值对方式携带请求参数,而是将要发送给服务器的数据作为 URL 地址的一部分,以保证整体风格的一致性

一个资源就对应一个 URL,不同的操作用不同的 HTTP 请求方法区分

这样做也有一个好处,就是我们可以直接根据 HTTP 请求方法来判断当前请求想要对资源进行哪种类型的操作,从而进行统一的控制,比如让服务器上的所有资源只读。

坏处就是得得多写控制器了,因为一个控制器只能对应一个 URL,虽然@RequestMapping 中可以支持通配符,但是要获取路径中的参数,所以没办法用一个控制器处理访问统一资源带不同个数的参数的情况,只能一种参数个数写一个控制器

操作 传统方式 REST 风格
查询操作 getUserById?id=1 user/1 配合 get 的请求方法
保存操作 saveUser user   配合 post 的请求方法
删除操作 deleteUser?id=1 user/1 配合 delete 的请求方法
更新操作 updateUser user   配合 put 的请求方法

如何 put 和 delete 请求呢?

现在我们来看看第三种方式怎么实现,了解即可,因为实际情况下这种方式用的不多。

HiddenHttpMethodFilter 处理 put 和 delete 请求的条件:

  1. 当前请求的请求方式必须为 post

  2. 当前请求必须传输请求参数 _method

    当然我们可以通过配置自定义这个参数

满足以上条件,HiddenHttpMethodFilter 过滤器就会将当前请求的请求方式转换为请求参数 _method 的值,因此请求参数 _method 的值才是最终的请求方式

实践

在 web.xml 中注册HiddenHttpMethodFilter

<!--配置springMVC的编码过滤器-->
<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>forceRequestEncoding</param-name>
        <param-value>true</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>


<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 = "/mockDelete",method = RequestMethod.DELETE)
public String mockDelete(String _method, String nameParam, Model model) {
    //设置视图名称
    model.addAttribute("mockFlag", true);
    model.addAttribute("params", _method+"----"+nameParam);
    return "index";

}

前端测试页面

<form method="post" th:action="@{/mockDelete}">
  <input type="hidden" name="_method" value="delete" />
  <span>用户名:</span><input type="text" name="nameParam" placeholder="nameParam"/><br/>
  <input type="submit" value="测试HiddenHttpMethodFilter"/>
</form>

SpringMVC Test 测试

注意:SpringMVC Test 框架,无法直接使用 web.xml 中的配置,只能手动在 MockMvcBuilders 的时候添加过滤器,试了下没成功,懒得搞了

不使用 post 方法 + 过滤器测试,而是直接使用 delete 方法测试

@Test
void mockDelete() throws Exception {
    // 无法使用 web.xml中的配置,只能手动在MockMvcBuilders的时候添加过滤器,试了下没成功,懒得搞了
    //mockMvc.perform(
    //        post("/mockDelete")
    //                // 最准确的测试肯定是指定请求体类型和请求体内容
    //                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
    //                .content("_method=delete&nameParam=xiashuo")
    //                // 方便起见,还是直接使用 parma参数吧
    //                //.param("_method","delete")
    //                //.param("nameParam","xiashuo")
    //        )
    //        .andExpect(model().attribute("mockFlag",true))
    //        .andDo(print());
    mockMvc.perform(
                    delete("/mockDelete")
                            // 最准确的测试肯定是指定请求体类型和请求体内容
                            .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                            .content("_method=delete&nameParam=xiashuo")
                    // 方便起见,还是直接使用 parma参数吧
                    //.param("_method","delete")
                    //.param("nameParam","xiashuo")
            )
            .andExpect(model().attribute("mockFlag",true))
            .andDo(print());
}

输出,可以很清楚地看到 Model 中的属性

MockHttpServletRequest:
      HTTP Method = DELETE
      Request URI = /mockDelete
       Parameters = {_method=[delete], nameParam=[xiashuo]}
          Headers = [Content-Type:"application/x-www-form-urlencoded", Content-Length:"32"]
             Body = <no character encoding set>
    Session Attrs = {}

Handler:
             Type = xyz.xiashuo.springmvcrestful.HelloController
           Method = xyz.xiashuo.springmvcrestful.HelloController#mockDelete(String, String, Model)

Async:
    Async started = false
     Async result = null

Resolved Exception:
             Type = null

ModelAndView:
        View name = index
             View = null
        Attribute = mockFlag
            value = true
        Attribute = params
            value = delete----xiashuo

FlashMap:
       Attributes = null

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Language:"en", Content-Type:"text/html;charset=UTF-8"]
     Content type = text/html;charset=UTF-8
             Body = <!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>首页</title>
</head>
<body>
<h1>首页</h1>
Hello World!
</br>
<form method="post" action="/mockDelete">
  <input type="hidden" name="_method" value="delete" />
  <span>用户名:</span><input type="text" name="nameParam" placeholder="nameParam"/><br/>
  <input type="submit" value="测试HiddenHttpMethodFilter"/>
</form>
</body>
</html>
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

HiddenHttpMethodFilter 注册的顺序

目前为止,SpringMVC 中提供了两个过滤器:CharacterEncodingFilter 和 HiddenHttpMethodFilter,在 web.xml 中注册时,必须先注册 CharacterEncodingFilter,再注册 HiddenHttpMethodFilter

在 web.xml 中指定过滤器的调用顺序,是由 <filter-mapping> 标签决定的,哪个 <filter><filter-mapping> 元素在前面则哪个 <filter> 首先被调用。但是,如果仅使用@WebFilter 注解,是无法指定过滤器的顺序的,作为变通,我们可以结合@WebFilter 和 web.xml 中的标签一起使用

原因:

CharacterEncodingFilter 中通过 request.setCharacterEncoding(encoding) 方法设置字符集的 request.setCharacterEncoding(encoding) 方法要求前面不能有任何获取请求参数的操作,而 HiddenHttpMethodFilter 恰恰有一个获取请求方式的操作:

String paramValue = request.getParameter(this.methodParam);

其实 HiddenHttpMethodFilter 总共支持三种 _method 参数值,PUT 、 PATCH 、 DELETE

RESTful 实际案例

这里就不贴了,直接看 Github,也很简单,不仅是对 RESTful 风格的使用,也是第之前所学的一个总结。

RESTful 风格接口常用注解

有关@ResponseBody 注解请看《SpringMVC- 第三篇:控制器方法获取请求参数和构建响应》中的@ResponseBody 章节,

有关响应的消息体的 MediaType 请看《SpringMVC-ContentNegotiation 内容协商》

@RestController

@RestController = @Controller + @ResponseBody

@RestControllerAdvice

@RestControllerAdvice = @ControllerAdvice + @ResponseBody