<h4 id="理解-REST"><a href="#理解-REST" class="headerlink" title="理解 REST"></a>理解 REST</h4><p><strong>REST(Representational State Transfer)</strong>,中文翻译叫“表述性状态转移”。是 <a href="https://en.wikipedia.org/wiki/Roy_Fielding" target="_blank" rel="external"><span style="color:blue;font-size:15px;font-family:Microsoft YaHei;font-style:oblique;">Roy Thomas Fielding</span></a>  在他2000年的<a href="http://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm" target="_blank" rel="external">博士论文</a>中提出的。它与传统的 SOAP Web 服务区别在于,REST关注的是要处理的数据,而 SOAP 主要关注行为和处理。要理解好 REST,根据其首字母拆分出的英文更容易理解。<br><strong>表述性(Representational):</strong>对于 REST 来说,我们网络上的一个个URI资源可以用各种形式来表述,例如:XML、JSON或者HTML等。<br><strong>状态(State):</strong>REST 更关注资源的状态而不是对资源采取的行为。<br><strong>转移(Transfer):</strong>在网络传输过程中,REST 使资源以某种表述性形式从一个应用转移到另一个应用(如从服务端转移到客户端)。</p>

具体来说,REST 中存在行为,它的行为是通过 HTTP 表示操作的方法来定义的即:GET、POST、PUT、DELETE、PATCH;GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源,PATCH用来更新资源。 基于 REST 这样的观点,我们需要避免使用 REST服务、REST Web服务 这样的称呼,这些称呼多少都带有一些强调行为的味道。


使用 RESTful 架构设计使用误区

RESTful 架构:是基于 REST 思想的时下比较流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

  • 在没有足够了解 REST 的时候,我们很容易错误的将其视为 “基于 URL 的 Web 服务”,即将 REST 和 SOAP 一样,是一种远程过程调用(remote procedure call,RPC)的机制。但是 REST 和 RPC 几乎没有任何关系,RPC 是面向服务的,而 REST 是面向资源的,强调描述应用程序的事物和名词。这样很容易导致的一个结果是我们在设计 RESTful API 时,在 URI 中使用动词。例如:GET /user/getUser/123。正确写法应该是 GET /user/123。

使用 springMVC 支持 RESTful

在 spring 3.0 以后,spring 这对 springMVC 的一些增强功能对 RESTful 提供了良好的支持。在4.0后的版本中,spring 支持一下方式创建 REST 资源:

  1. 控制器可以处理所有的 HTTP 方法,包含几个主要的 REST 方法:GET、POST、PUT、DELETE、PATCH;
  2. 借助 @PathVariable 注解,控制器能够处理参数化的 URL(将变量输入作为 URL 的一部分);
  3. 借助 spring 的视图解析器,资源能够以多种方式进行表述,包括将模型数据渲染为 XML、JSON、Atom、已经 RSS 的 View 实现;
  4. 可以使用 ContentNegotiatingViewResolver 来选择最适合客户端的表述;
  5. 借助 @ResponseBody 注解和各种 HttpMethodConverter 实现,能够替换基于视图的渲染方式;
  6. 类似地,@RequestBody 注解以及 HttpMethodConverter 实现可以将传入的 HTTP 数据转化为传入控制器处理方法的 Java 对象;
  7. 借助 RestTemplate ,spring 应用能够方便地使用 REST 资源。

创建 RESTful 控制器

代码清单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.pengdh.controller;
import com.pengdh.entity.EmployeeEntity;
import com.pengdh.service.EmployeeService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
/**
* @author pengdh
* @date: 2017-06-27 0:08
*/
@Controller
@RequestMapping("/employs")
public class EmployeeController {
@Autowired
private EmployeeService empService;
@GetMapping(value = "/list", produces = { "application/json;charset=UTF-8" })
public List<EmployeeEntity> employs(Integer offset,Integer limit) {
offset = offset == null ? 0 : offset;
limit = limit == null ? 20 : limit;
return empService.queryEmployList(offset,limit);
}
}

代码的大致过程是当客户端发起对 “/employs” 的 GET 请求时,将调用服务端的 employs 方法,服务端通过注入的 EmployeeService 获取到一个 EmployeeEntity 列表,并将列表以 JSON 的表述形式返回给客户端。

  • 需要注意的是这里控制器本身并不关心资源如何表述。控制器以 Java 对象的方式来处理资源。控制器完成了它的工作以后,资源才会被转化成为适合客户端的形式。spring 提供了两种方法将资源的 java 表述形式转化为发送给客户端的表述形式:
    • 内容协商(Content negotiation):选择一个视图,它能够将模型渲染为呈现给客户端的表述形式;
    • 消息转化器(Message conversion):通过一个消息转换器将控制器所返回的对象转换为呈现给客户端的表述形式。

对于上述两种方式,第一种方式是通过 ContentNegotiatingViewResolver 作为 ViewResolver 的实现,主要是用于将资源渲染人类用户接口所需要的视图模型,如:HTML、JSP等也可以渲染。也可以针对不是人类客户端产生 JSON 或 XML,但是效果不是很理想,往往会产生一些不是客户端所需要的预期结果。如:客户端希望得到的响应可能是:{“name”:”zhangs”,”age”:”20”}。而模型是 key-value 组成的 map ,可能最终的响应是这样的:{“user”:{“name”:”zhangs”,”age”:”20”}}。基于内容协商的这些限制,这里我们主要讨论第二种方式:使用 Spring 的消息转换功能来生成资源表述。


使用 HTTP 消息转换器

这是一种更为直接的方式,消息转换器能够将控制器产生的数据转换为服务于客户端的表述形式。常用的一些消息转换器如:Jackson 的 MappingJacksonHttpMessageConverter 实现 JSON 消息和 Java 对象的互相转换; JAXB 库的 Jaxb2RootElementHttpMessageConverter 实现 XML 和 Java 对象的相互转换等。

通过 @ResponseBody 注解实现响应体中返回资源状态。

正常情况下,当处理方法返回 Java 对象时,这个对象会放在模型中并在视图中渲染使用。但是,如果使用了消息转换功能的话,我们需要告诉 Spring 跳过正常的模型/视图流程,并使用消息转换器。实现这种方式最简单的方式是在控制器的方法上添加 @ResponseBody 注解。如:

1
2
3
4
5
6
7
8
@GetMapping(value = "/list", produces = { "application/json;charset=UTF-8" })
@ResponseBody
public List<EmployeeEntity> employs(Integer offset,Integer limit) {
offset = offset == null ? 0 : offset;
limit = limit == null ? 20 : limit;
return empService.queryEmployList(offset,limit);
}

这里 @ResponseBody 注解会告知 Spring 将 List 转换成 JSON 这样的表述形式作为资源发送给客户端。

使用 @RequestBody 注解实现在请求体中接收资源状态

使用 @RequestBody 注解可以告知 Spring 查找一个消息转换器,将来自客户端的资源表述转换为对象。如:

1
2
3
4
@PostMapping(value = "/employ", produces = { "application/json;charset=UTF-8" })
public int saveEmploy(@RequestBody EmployeeEntity employeeEntity) {
return empService.save(employeeEntity);
}
使用 @RestController 注解为控制器默认设置消息转换

Spring 4.0 引入了 @RestController 注解,在控制器是用 @RestController 代替 @Controller 的话,Spring 将会为该控制器的所有处理方法应用消息转换功能。我们不必在每个方法都添加 @ResponseBody 注解了。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.pengdh.controller;
import com.pengdh.entity.EmployeeEntity;
import com.pengdh.service.EmployeeService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pengdh
* @date: 2017-06-27 0:08
*/
@RestController
@RequestMapping("/employs")
public class EmployeeController {
@Autowired
private EmployeeService empService;
@GetMapping(value = "/list", produces = { "application/json;charset=UTF-8" })
public List<EmployeeEntity> employs(Integer offset,Integer limit) {
offset = offset == null ? 0 : offset;
limit = limit == null ? 20 : limit;
return empService.queryEmployList(offset,limit);
}
@PostMapping(value = "/employ", produces = { "application/json;charset=UTF-8" })
public int saveEmploy(@RequestBody EmployeeEntity employeeEntity) {
return empService.save(employeeEntity);
}
}

为客户端提供其他元数据

使用 ResponseEntity 提供更多响应相关的元数据

可以利用 ResponseEntity 给客户端返回状态码、设置响应头信息等,如给客户端提供返回码:

1
2
3
4
5
6
7
8
9
10
11
@GetMapping(value = "/{id}", produces = { "application/json;charset=UTF-8" })
public ResponseEntity<EmployeeEntity> employById(@PathVariable long id) {
HttpStatus status = null;
EmployeeEntity employeeEntity = empService.selectById(id);
if (employeeEntity != null) {
status = HttpStatus.OK;
} else {
status = HttpStatus.NOT_FOUND;
}
return new ResponseEntity<EmployeeEntity>(employeeEntity, status);
}

如果没有 if 判断,当根据 id 找不到对应的信息的时候,返回给客户端的状态码是默认的 HttpStatus.OK;当加上了判断条件后如果没有相应的信息返回则设置返回状态码为 HttpStatus.NOT_FOUND,最后通过 new 一个 ResponseEntity 会将查询信息和状态码一起返回到客户端。

  • 另外,ResponseEntity 还包含有 @ResponseBody 的语义,上面示例中并没有使用 @ResponseBody 注解,但是 ResponseEntity 的负载部分同样可以渲染到响应体中。
使用控制器异常处理器 @ExceptionHandler 处理异常信息

@ExceptionHandler 可以用到控制器的方法中,处理特定的异常:

创建响应包装类 ResponseResult

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package com.pengdh.dto;
import java.io.Serializable;
/**
* 响应结果封装类
*
* @author pengdh
* @date: 2017-06-29 0:34
*/
public class ResponseResult<T> implements Serializable {
private static final long serialVersionUID = -3371934618173052904L;
private String code;
private String desc;
private T data;
public ResponseResult() {
}
public ResponseResult(String code, String desc) {
this.code = code;
this.desc = desc;
}
public ResponseResult(String code, T data) {
this.code = code;
this.data = data;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getDesc() {
return desc;
}
public void setDesc(String desc) {
this.desc = desc;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
@Override
public String toString() {
return "ResponseResult{" +
"code='" + code + '\'' +
", desc='" + desc + '\'' +
", data=" + data +
'}';
}
}

创建一个异常类 ResourceNotFoundException

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.pengdh.exception;
/**
* 资源未找到异常
*
* @author pengdh
* @date: 2017-06-29 0:55
*/
public class ResourceNotFoundException extends RuntimeException {
private static final long serialVersionUID = 4880328265878141724L;
public ResourceNotFoundException() {
super();
}
public ResourceNotFoundException(String message) {
super(message);
}
public ResourceNotFoundException(String message, Throwable cause) {
super(message, cause);
}
}

控制器 EmployeeController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.pengdh.controller;
import com.pengdh.dto.ResponseResult;
import com.pengdh.entity.EmployeeEntity;
import com.pengdh.exception.ResourceNotFound;
import com.pengdh.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pengdh
* @date: 2017-06-27 0:08
*/
@RestController
@RequestMapping("/employs")
public class EmployeeController {
@Autowired
private EmployeeService empService;
@GetMapping(value = "/{id}", produces = { "application/json;charset=UTF-8" })
public ResponseResult<EmployeeEntity> employById(@PathVariable long id) {
ResponseResult<EmployeeEntity> result = new ResponseResult<EmployeeEntity>();
HttpStatus status = null;
EmployeeEntity employeeEntity = empService.selectById(id);
if (employeeEntity == null) {
throw new ResourceNotFoundException(String.valueOf(id));
}
result.setCode(String.valueOf(HttpStatus.OK));
result.setData(employeeEntity);
return result;
}
@ExceptionHandler(ResourceNotFoundException.class)
public ResponseResult<Object> handlerException(ResourceNotFoundException e) {
ResponseResult<Object> result = new ResponseResult<Object>();
result.setCode(String.valueOf(HttpStatus.NOT_FOUND));
result.setDesc(e.getMessage());
return result;
}
}

从控制器代码可以看出,我们通过 @ExceptionHandler 能将控制器的方法的异常场景分出来单独处理。


使用 @RestControllerAdvice 捕获所有 controller 抛出的异常

@ ControllerAdvice是一个@ Component,用于定义@ ExceptionHandler的,@InitBinder和@ModelAttribute方法,适用于所有使用@ RequestMapping方法。

新建一个异常处理类 GlobalExceptionHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.pengdh.exception;
import com.pengdh.dto.ResponseResult;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 全局异常处理器
*
* @author pengdh
* @date: 2017-07-11 0:00
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseResult<Object> handlerException(ResourceNotFoundException e) {
return new ResponseResult<Object>(String.valueOf(HttpStatus.NOT_FOUND), e.getMessage());
}
}

控制器 EmployeeController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package com.pengdh.controller;
import com.pengdh.dto.ResponseResult;
import com.pengdh.entity.EmployeeEntity;
import com.pengdh.exception.ResourceNotFoundException;
import com.pengdh.service.EmployeeService;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author pengdh
* @date: 2017-06-27 0:08
*/
@RestController
@RequestMapping("/employs")
public class EmployeeController {
@Autowired
private EmployeeService empService;
@GetMapping(value = "/list", produces = { "application/json;charset=UTF-8" })
public List<EmployeeEntity> employs(Integer offset,Integer limit) {
offset = offset == null ? 0 : offset;
limit = limit == null ? 20 : limit;
return empService.queryEmployList(offset,limit);
}
@PostMapping(value = "/employ", produces = { "application/json;charset=UTF-8" })
public int saveEmploy(@RequestBody EmployeeEntity employeeEntity) {
return empService.save(employeeEntity);
}
@GetMapping(value = "/{id}", produces = { "application/json;charset=UTF-8" })
public ResponseResult<EmployeeEntity> employById(@PathVariable long id) {
ResponseResult<EmployeeEntity> result = new ResponseResult<EmployeeEntity>();
HttpStatus status = null;
EmployeeEntity employeeEntity = empService.selectById(id);
if (employeeEntity == null) {
throw new ResourceNotFoundException(String.valueOf(id));
}
result.setCode(String.valueOf(HttpStatus.OK));
result.setData(employeeEntity);
return result;
}
}

从代码可以看出,我们将 controller 中的异常方法移到了一个新增的异常处理类。这样,如果其他 controller 类也抛出 ResourceNotFoundException 异常的时候,就会被 GlobalExceptionHandler 捕获。


参考文献

</div>

基于 springMVC 的 RESTful HTTP API 实践(服务端)的更多相关文章

  1. 基于springMVC的RESTful服务实现

    一,什么是RESTful RESTful(RESTful Web Services)一种架构风格,表述性状态转移,它不是一个软件,也不是一个标准,而是一种思想,不依赖于任何通信协议,但是开发时要成功映 ...

  2. API接口服务端

    <?php /** * API接口服务端 * * */ require 'mysql_class.php'; header('Content-Type:text/html;charset=utf ...

  3. 前后端分离开发,基于SpringMVC符合Restful API风格Maven项目实战(附完整Demo)!

    摘要: 本人在前辈<从MVC到前后端分离(REST-个人也认为是目前比较流行和比较好的方式)>一文的基础上,实现了一个基于Spring的符合REST风格的完整Demo,具有MVC分层结构并 ...

  4. RESTful Web API 实践

    REST 概念来源 网络应用程序,分为前端和后端两个部分.当前的发展趋势,就是前端设备层出不穷(手机.平板.桌面电脑.其他专用设备...). 因此,必须有一种统一的机制,方便不同的前端设备与后端进行通 ...

  5. 基于MVC的RESTFul风格API实战

    基于MVC的RESTful风格的实现 1.RESTful风格阐述 REST服务是一种ROA(Resource-Oriented Architecture,面向资源的架构)应用.主要特点是方法信息存在于 ...

  6. 基于vue的nuxt框架cnode社区服务端渲染

    nuxt-cnode 基于vue的nuxt框架仿的cnode社区服务端渲染,主要是为了seo优化以及首屏加载速度 线上地址 http://nuxt-cnode.foreversnsd.cngithub ...

  7. 轻易实现基于linux或win运行的聊天服务端程序

    对于不了解网络编程的开发人员来说,编写一个良好的服务端通讯程序是一件比较麻烦的事情.然而通过EC这个免费组件你可以非常简单地构建一个基于linux或win部署运行的网络服务程序.这种便利性完全得益于m ...

  8. Android应用源码基于安卓的校园二手交易系统客户端+服务端+数据库

    该源码是校园二手交易系统应用带服务端,也是一个基于安卓和javaweb的校园二手交易系统,包括整套安卓客户端.javaweb服务端.mysql数据库,可以进行基本的列表显示帖子.显示帖子详情.用户注册 ...

  9. T+API HTTPServer服务端

    该服务端是一个HTTP服务器,这样其他语言调用也方便. 出于某些原因,只支持Post方法,不打算支持其他方法,例如Get. API所接受的参数将以Json传送,回传的数据也是一个Json数据,一切只是 ...

随机推荐

  1. 深入浅析python中的多进程、多线程、协程

    深入浅析python中的多进程.多线程.协程 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源 ...

  2. invalid use of null value

    给mysql的数据表的一个字段插入数据,不成功, 然后在数据表设计中,把不是null勾选上,又提示 invalid use of null value 这种情况比较尴尬 只能删掉这一个字段,然后新建一 ...

  3. GIT → 05:Git命令行操作

    5.1 打开命令行窗口 安装Git后,在资源管理器的空白处,单击鼠标右键打开窗口,点击 Git Bash Here ,打开Git命令行窗口,在窗口中可直接使用Linux命令操作: 5.2 初始化Git ...

  4. Leetcode96.Unique Binary Search Trees不同的二叉搜索树

    给定一个整数 n,求以 1 ... n 为节点组成的二叉搜索树有多少种? 示例: 输入: 3 输出: 5 解释: 给定 n = 3, 一共有 5 种不同结构的二叉搜索树: 假设n个节点存在二叉排序树的 ...

  5. 关于Vector CANoe的讨论

    默认排序​ 踩猫尾巴 汽车电子攻城狮 27 人赞同了该回答 好像是很久以前的问题啊,为什么会现在收到邀请. 我觉得 @lijuqqkiko 介绍的足够啦. 我再额外发散一点吧. 目前在CAN总线测试和 ...

  6. Hdu 2389 二分匹配

    题目链接 Rain on your Parade Time Limit: 6000/3000 MS (Java/Others)    Memory Limit: 655350/165535 K (Ja ...

  7. 确定比赛名次 HDU - 1285 (拓扑排序)

     注意点: 输入数据中可能有重复,需要进行处理! #include <stdio.h> #include <iostream> #include <cstring> ...

  8. 【JZOJ3623】【SDOI2014】数表(table) 树状数组+离线+莫比乌斯反演

    题面 100 \[ Ans=\sum_{i=1}^n\sum_{j=1}^mg(gcd(i,j)) \] 其中, \[ g(d)=\sum_{i|d}i \] 我们注意到\(gcd(i,j)\)最多有 ...

  9. (实现)vue.js最简实现

    Vue.winward.js vue.js最简实现(the most simple vue.js) 让所有人都看得懂Vue原理 建议看完Vue.winward.js后,结合mpvue源码解读单页应用路 ...

  10. 抽象类 abstract class 接口

    一.抽象类 1.没有具体的实例. 不可实例化,不能创建对象. 2.抽象类有构造器. 二.abstract 方法. 1.没有方法体. 子类必须重写抽象类的所有抽象方法,才能实例化,否则子类也为抽象类. ...