# Spring MVC 对 RESTful 的支持

# 1. PUT、PATCH 的参数问题

RESTful API 要求服务端能响应:get / post / put / delete 请求,以对应增删改查四大功能。对此 Spring MVC 都能支持,只需要在请求处理方法头上加上:

@RequestMapping(value="/...", method = RequestMethod.GET)

或者使用:

@GetMapping("/...")
@PostMapping("/...")
@PutMapping("/...")
@DeleteMapping("/...")

就能表示该方法仅针对于特定请求方式作出响应。

默认 Spring MVC 可以接受 PUT 请求和 PATCH 请求,但是,不接受 PUT 请求和 PATCH 请求发送来参数!!!

Spring MVC 通过 @RequestParam 注解接收的请求只有两类:

  1. 接收 GET 请求提交的参数。

  2. 接收 application/x-www-form-urlencoded 方式的 POST 请求提交的参数。

  3. 不支持 PUT、PATCH(也包括 DELETE)请求的参数的获取。

当然,究其原因这个锅不该 Spring MVC 背:

  1. Servlet 的 request.getParameter(""); 本身就只支持 GET 和 POST 方法传参。

  2. Servlet API 中并没有 doPatch() 方法,更谈不上获取请求参数了。

# 2. 解决方案

解决 PUT、PATCH 的参数传递的方案有两种:

# 方案一:使用特定 Filter 处理

Spring MVC 从 3.1 开始提供了一个 Filter(过滤器)来解决这个传参问题。

  • 3.1 开始,提供了一个名为 HttpPutFormContentFilter

  • 5.1 开始,又提供了一个名为 FormContentFilter 的新的 Filter 来替代 HttpPutFormContentFilter 。which is the same but also handles DELETE` 。

<filter>	<!-- 默认 Spring MVC 无法接受 PUT 请求参数 -->
    <filter-name>httpPutFormContentFilter</filter-name>
    <filter-class>
      org.springframework.web.filter.HttpPutFormContentFilter
    </filter-class>
</filter>

<filter-mapping>
    <filter-name>httpPutFormContentFilter</filter-name>
    <url-pattern>/*</url-pattern>   
    <!-- 注意匹配规则,这里是 /* ,表示拦截所有请求 -->
</filter-mapping>

这两个过滤器会拦截所有的 PUT 和 PATCH 请求,将它们的底层处理方式转变为与 POST 请求相同的处理方式。这样你才可以在 PUT 和 PATCH 请求的处理方法中使用 @RequestParm 注解。(否则,该注解使用无效)。

# 方法二:使用 application/json 曲线救国

PUT 和 PATCH 的请求参数默认是以 x-www-form-urlencodedcontentType 来发送信息,即请求参数是放在 request 的 body 中,以 aaa=xxx&bbb=yyy&ccc=zzz

在默认情况下,@ReqeustParam 是不会去获取它们在 body 中的请求参数的(除非采用方案一,挂羊头卖狗肉)

方案二的本质就是索性 不使用 @RequestParam 获取参数,而使用 @RequestBody

利用 application/json 方式传递请求参数,将参数以 JSON 格式字符串的形式放在 request 的 body 中,在 Controller 中再配合 @RequestBody 进行参数绑定,从而获取参数。绕开 @RequestParam / request.getParam() 的限制。

# 3. 批量删除时传入多个 id 实现

# 方案一:@PathVariable

接收用数组,调用 api 时,url 后面可跟多个 id,用逗号隔开,如:localhost/user/1234,1235,1236

@DeleteMapping("/{id}")
public JsonData delete(@PathVariable("id") String[] userIds) {
    userService.delete(userIds);
    return JsonData.ok();
}

# 方案二:利用 FormContentFilter 强用 @RequestParam

在使用 FormContentFilter 后,DELETE 请求的底层处理方式会被 Spring MVC 偷换成 POST 方式,这样,实际上就完全可以像 POST 请求那样传递多 id 。

@DeleteMapping("/delete")
public String delete(int[] ids) {
    log.info("[{}]", Arrays.toString(ids));
    return "hello";
}

客户端发出的请求参数类似是:?ids=1&ids=2&ids=3

# 4. ResponseEntity<T> 类型

在有的 Rest-ful 风格的项目中,是通过设置响应状态码来告知请求方请求是否成功。这种情况下,自然就不能始终使用默认的 200 作为响应状态码,而是要手动设置一个符合逻辑的状态码。

ResponseEntity 返回值类型能实现这样的功能。Spring MVC 中返回的 ResponseEntity 对象,一方面包含了 HTTP 响应信息,另一方面还包含返回给请求方的数据。

Spring MVC 发现你的方法返回的是 ResponseEntity 类型的对象时,它会做如下 2 件事情:

  1. 将 ResponseEntity 对象中所设置的『响应状态码』信息作为本次 HTTP 请求地响应状态码。
  2. 将 ResponseEntity 对象中所设置的『返回数据』转换为 JSON 格式字符串后,携带在 HTTP 响应地 Body 中返回。
@RequestMapping("/test1")
public ResponseEntity<List<Department>> test1(){
   List<Department> list = ...; 
   return ResponseEntity.ok(list); // 等同于
// return ResponseEntity.status(HttpStatus.OK).body(dept);
}

@RequestMapping("/test2")
public ResponseEntity<String> test2(){
    return ResponseEntity.badRequest().body("..."); // 等同于
//  return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(...)
}

再强调一遍,标注了 @ResponseEntity 注解,就不需要再标注 @ResponseBody 注解 。