RESTful 风格(详细介绍 + 案例实现)
一、什么是 API(应用程序接口)
概念
API,英文全称Application Programming Interface,翻译为“应用程序编程接口”。就是将一些功能(逻辑)封装成组件,目的是提供一个应用程序接口给其它程序与开发人员访问,而这些访问人员不需要访问源码以及理解内部工作原理就可以直接使用。
举例
在 WEB 项目中 A 应用暴露一个请求映射方法,B应用通过调用这个请求映射方法从而得到对应功能(请求映射方法赋予的功能)
二、传统模式和前后端分离模式对比

1. 传统开发模式
在传统开发模式的应用中,前端写好静态的html页面交给后端开发,后端把html渲染或重定向到前端界面。这样前端页面看到的效果都是由后端控制的,也就是后端需要控制前端的展示,这样会出现很多严重的问题。
弊端
【1】前后端耦合严重,前端会嵌入后端代码,导致代码混乱,可维护性差;
【2】开发出的软件响应速度慢,质量差,用户体现差;
【3】开发人员需要前后端兼顾,开发效率低下,开发周期变长;
【4】与前端开发人员之间沟通成本高,前后端开发进度相互影响,从而大大降低开发效率。
2. 前后端分离模式
前后端分离并不只是开发模式,也是web应用的一种架构模式。在开发阶段,前后端人员约定好数据交互接口,即可并行开发与测试。
前端开发完成可以独自进行mock测试,后端也可以使用postman等接口测试工具进行测试。最后可进行功能联调测试。
说通俗点就是后端项目里面看不到页面(JSP|HTML),后端给前端提供接口,前端调用后端提供的REST风格接口就行,前端专注写页面(html|jsp)和渲染(JS|CSS|各种前端框架);后端专注写代码就行。 前后端分离的核心:后台提供数据,前端负责显示
优点
【1】可以实现真正的前后端解耦,前后端可以并行开发与测试,提高开发效率;
【2】减少后端服务器的并发/负载压力,提高系统的性能;
【3】异步加载的方式,可以很好的应对复杂多变的前端需求;
【4】前后端代码不再混乱,增强了代码的可维护性。
三、RESTful 风格
1. 概念
RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,使用JSON格式的REST风格的API具有简单、易读、易用的特点。
2. 资源
REST 是面向资源的,每个资源都有一个唯一的资源定位符(URI)。每个URI代表一种资源(resource),所以URI中不能有动词,只能有名词,而且所用的名词往往与数据库的表名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以URI中的名词也应该使用复数。
例如:
// 查询所有员工 @RequestMapping(value = "/employees", method = RequestMethod.GET) @ResponseBody public List<Employee> list() { ... }
3. 请求方式
| HTTP 方法 | 作用 |
|---|---|
| GET | 获取资源 |
| POST | 创建资源 |
| PUT | 更新资源(整体) |
| PATCH | 更新资源(部分) |
| DELETE | 删除资源 |
4. 传统模式 URI 和 RESTful 风格对比
【1】查询
| 查询 | 传统 | REST | REST 后台接收 |
|---|---|---|---|
| 查询所有 | http://localhost:8080/employee/list | http://localhost:8080/employees | @RequestMapping(value = “/employees”, method = RequestMethod.GET) |
| 查询单个 | http://localhost:8080/employee/list?id=1 | http://localhost:8080/employees/1 | @RequestMapping(value = “/employees/{id}”, method = RequestMethod.GET) @ResponseBody public Employee queryById(@PathVariable Long id) {} |
【2】添加
| 添加 | 传统 | REST | REST 后台接收 |
|---|---|---|---|
| 添加 | http://localhost:8080/employee/add | http://localhost:8080/employees | @RequestMapping(value = “/employees”, method = RequestMethod.POST) public Employee add(@ModelAttribute(“emp”) Employee employee) {} |
【3】修改
| 添加 | 传统 | REST | REST 后台接收 |
|---|---|---|---|
| 添加 | http://localhost:8080/employee/add | http://localhost:8080/employees | @RequestMapping(value = “/employees”, method = RequestMethod.POST) public Employee add(@ModelAttribute(“emp”) Employee employee) {} |
【4】删除
| 查询 | 传统 | REST | REST 后台接收 |
|---|---|---|---|
| 删除 | http://localhost:8080/employee/delete | http://localhost:8080//employees/{id} | @RequestMapping(value = “/employees/{id}”, method = RequestMethod.DELETE) @ResponseBody public JsonResult delete(@PathVariable Long id) {} |
注意:
【1】当参数非常多的时候,不建议使用参数路径方式;
【2】如果参数名非常敏感,建议使用参数路径方式,可以隐藏参数名。
5. 返回值-按需求决定
GET /collection:返回资源对象的列表(数组)
GET /collection/resource:返回单个资源对象
POST /collection:返回新生成的资源对象
PUT /collection/resource:返回完整的资源对象
PATCH /collection/resource:返回完整的资源对象
DELETE /collection/resource:返回一个空文档
6、常见 RESTful API 错误码(建议)
| 状态码 | 含义 | 说明 |
|---|---|---|
| 200 | OK | 请求成功 |
| 201 | Created | 成功创建资源 |
| 204 | No Content | 请求成功,但无返回内容 |
| 400 | Bad Request | 请求参数有误 |
| 401 | Unauthorized | 认证失败 |
| 403 | Forbidden | 没有权限访问资源 |
| 404 | Not Found | 资源不存在 |
| 500 | Internal Server Error | 服务器内部错误 |
7. 同一个资源具有多种表现形式(xml,json等)
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现"的描述。
accept:application/json
content-type:application/json
Accept与Content-Type的区别
【1】Accept属于请求头, Content-Type属于实体头。
Http报头分为通用报头,请求报头,响应报头和实体报头。
请求方的http报头结构:通用报头|请求报头|实体报头
响应方的http报头结构:通用报头|响应报头|实体报头
【2】Accept代表发送端(客户端)希望接受的数据类型。
比如:Accept:application/json;
代表客户端希望接受的数据类型是json类型,后台返回json数据;
【3】Content-Type代表发送端(客户端|服务器)发送的实体数据的数据类型。
比如:Content-Type:application/json;
代表发送端发送的数据格式是json, 后台就要以这种格式来接收前端发过来的数据。
8. 使用Ajax来发送各种请求方法的请求
$.get('/employees',function(){})
$.post('/employees',params,function(){})
$.ajax({
url:"/employees/1",
type:"put",
data:params
success:function(){}
})
$.ajax({
url:"/employees/1",
type:"DELETE",
success:function(){}
})
- SpringBoot 2.2.x 中 Put 请求和 Delete 请求不起作用
# application.properties 中配置 spring.mvc.hiddenmethod.filter.enabled=true
- springMVC默认不支持处理put请求,需要配置处理put或patch请求方式的过滤器
<filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
SpringMVC 中的 delete 请求
$.ajax({ type: "post", url:url, data: {"contentId": id, "_method": "delete"}, success: function (data) { if (data.status == 0) { alert("成功!"); location.reload(); } else { alert("操作失败!" + data.reason); } } });
9. 相关注解
| 注解 | 作用 |
|---|---|
| @RestController | 由 @Controller + @ResponseBody组成(返回 JSON 数据格式) |
| @PathVariable | URL 中的 {xxx} 占位符可以通过@PathVariable(“xxx“) 绑定到控制器处理方法的形参中 |
| @RequestMapping | 注解用于请求地址的解析,是最常用的一种注解 |
| @GetMapping | 查询请求 |
| @PostMapping | 添加请求 |
| @PutMapping | 更新请求 |
| @DeleteMapping | 删除请求 |
| @RequestParam | 将请求参数绑定到你控制器的方法参数上(是springmvc中接收普通参数的注解) |
10. RequestMapping标签的属性
value/path:映射路径;
method:限定请求的方式,枚举:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
}
@GetMapping(value = "list",params="version=1") public Object list() { return "ok"; }
- headers:限定要处理请求的请求头信息,只有匹配该请求头内容的请求,才会被该方法处理;
@GetMapping(value = “/test”, headers = “content-type=text/*”)
四、例题
1. 前端界面
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="../js/jquery.min.js"></script>
<script>
$(function () {
// 查询所有
$('#btn1').click(function () {
$.get('/employees', function (data) {
console.log(data);
});
});
// 查询一个
$('#btn2').click(function () {
$.get('/employees/10', function (data) {
console.log(data);
});
});
// 添加
$('#btn3').click(function () {
$.post('/employees', "id=11", function (data) {
console.log(data);
});
});
// 修改
$('#btn4').click(function () {
$.ajax({
url: "/employees",
type: "PUT",
data: {id: 1, name: "小肥羊", age: 10},
success: function (data) {
console.log(data);
}
});
});
// 删除
$('#btn5').click(function () {
$.ajax({
url: "/employees/13",
type: "DELETE",
data: {id: 1},
success: function (data) {
console.log(data);
}
});
});
});
</script>
</head>
<body>
<button id="btn1">查询所有</button>
<button id="btn2">查询一个</button>
<button id="btn3">添加</button>
<button id="btn4">修改</button>
<button id="btn5">删除</button>
</body>
</html>
2. 控制器
package com.yy.web.controller; import com.yy.domain.Employee; import com.yy.util.JsonResult; import org.springframework.stereotype.Controller; import org.springframework.stereotype.Repository; import org.springframework.web.bind.annotation.*; import java.util.Arrays; import java.util.List; /** * @program: restful-demo * @ClassName: EmployeeController * @description: * @author: YanYang * @create: 2021-06-23 15:04 **/ @RestController @RequestMapping("employees") public class EmployeeController { /** * 2个接口设计都是用相同资源,相同请求方法,此时 SpringMVC 无法识别, * 认为是同一方法,报错:Ambiguous mapping. [模糊映射] * * RESTful 解决方案:使用 参数路径 方式 * 具体实现:将参数作为请求映射路径一部分,[参与映射路径区分] * 比如:查询指定 id 的员工信息接口设计 * @RequestMapping(value = "/employees/{id}", method = RequestMethod.GET) * 其中 "/employees/{id}" 参数路径,{id} 就是路径参数 * * 访问该接口时:http:localhost:8080/employees/1 其中 1 是 id 参数 * 接口接收路径参数:使用 @PathVariable,表示将参数路径上的参数解析,并赋值给指定变量 * 如果路径参数与变量名不一致,使用 @PathVariable("eid")明确指定 * */ // 查询所有(数据是模拟的) // @RequestMapping(value = "/employees", method = RequestMethod.GET) @GetMapping public List<Employee> list() { return Arrays.asList(new Employee(1L,"小肥羊", 10), new Employee(2L, "熊大", 11)); } // 查询单个(数据是模拟的) // @RequestMapping(value = "/employees/{id}", method = RequestMethod.GET) @GetMapping("/{eid}") public Employee queryById(@PathVariable("eid") Long id) { System.out.println("查询单个 = " + id); return new Employee(3L, "熊二", 8); } // 添加(数据是模拟的) // @RequestMapping(value = "/employees", method = RequestMethod.POST) @PostMapping public Employee add(@ModelAttribute("employee") Employee employee) { System.out.println("添加 = " + employee.getId()); return employee; } // 修改(数据是模拟的) // @RequestMapping(value = "/employees", method = RequestMethod.PUT) @PutMapping public Employee update(@ModelAttribute("employee") Employee employee) { System.out.println("修改 = " + employee.getId()); employee.setId(employee.getId()); employee.setName(employee.getName()); employee.setAge(employee.getAge()); return employee; } // 删除(数据是模拟的) // @RequestMapping(value = "/employees/{id}", method = RequestMethod.DELETE) @DeleteMapping("/{id}") public String delete(@PathVariable Long id) { System.out.println("删除 = " + id); if (id != null && 1 == id) { return "删除成功"; } return "删除失败"; } }
3. 输出结果
- 控制台输出

- 浏览器打印

五、什么时候应该用 RESTful?
- 资源之间关系明确、结构层级清晰
- 系统需要公开标准 API 供外部系统调用
- 系统前后端分离,接口要语义化、易于文档化

浙公网安备 33010602011771号