RESTful 风格(详细介绍 + 案例实现)
Posted Yan Yang
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RESTful 风格(详细介绍 + 案例实现)相关的知识,希望对你有一定的参考价值。
这里写目录标题
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. 请求方式
请求方式 | 含义 |
---|---|
GET(SELECT) | 从服务器取出资源(一项或多项) |
POST(CREATE) | 在服务器新建一个资源 |
PUT(UPDATE) | 在服务器更新资源(更新完整资源) |
PATCH(UPDATE) | 在服务器更新资源, PATCH更新个别属性 |
DELETE(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/update | http://localhost:8080/employees | @RequestMapping(value = “/employees”, method = RequestMethod.PUT) public Employee update(@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. HTTP响应状态码
状态码 | 含义 |
---|---|
200 OK - [GET] | 服务器成功返回用户请求的数据 |
201 CREATED - [POST/PUT/PATCH] | 用户新建或修改数据成功 |
202 Accepted | 表示一个请求已经进入后台排队(异步任务) |
204 NO CONTENT - [DELETE] | 用户删除数据成功 |
400 INVALID REQUEST - [POST/PUT/PATCH] | 用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的 |
401 Unauthorized - [*] | 表示用户没有权限(令牌、用户名、密码错误) |
403 Forbidden - [*] | 表示用户得到授权(与401错误相对),但是访问是被禁止的 |
404 NOT FOUND - [*] | 用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的 |
405 Method Not Allowed | 方法不允许,服务器没有该方法 |
406 Not Acceptable - [GET] | 用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式) |
410 Gone -[GET] | 用户请求的资源被永久删除,且不会再得到的 |
422 Unprocesable entity - [POST/PUT/PATCH] | 当创建一个对象时,发生一个验证错误 |
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中接收普通参数的注解) |
@RequestParam语法
语法:@RequestParam(value=”参数名”,required=”true/false”,defaultValue=””)
value:参数名
required:是否包含该参数,默认为true,表示该请求路径中必须包含该参数,如果不包含就报错。
defaultValue:默认参数值,如果设置了该值,required=true将失效,自动为false,如果没有传该参数,就使用默认值
10. RequestMapping标签的属性
-
value/path:映射路径;
-
method:限定请求的方式,枚举:
public enum RequestMethod {
GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE
} -
params:限定要处理请求的参数,只有匹配该参数的请求,才会被该方法处理;
@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. 输出结果:
- 控制台输出
查询单个 = 10
添加 = 11
修改 = 1
删除 = 13
- 浏览器打印
以上是关于RESTful 风格(详细介绍 + 案例实现)的主要内容,如果未能解决你的问题,请参考以下文章