说起 REST API,小伙伴们多多少少都有听说过,可是假如让你具体介绍一下什么是 REST,估量会有许多人讲不出来,或许只讲出来其间一部分。
今天松哥就来和咱们一起来聊一聊究竟什么是 REST,顺便再来看下 Spring HATEOAS 的用法。
1. REST 成熟模型
首先关于 REST,有一个大佬 Leonard Richardson 为 REST 界说了一个成熟度模型,他总共界说了四个不同的层次,别离如下:
- Level0:Web 服务单纯的运用 HTTP 作为数据传输办法,本质上便是长途办法调用,常见的 SOAP 和 RPC 基本上都属于这一类。
- Level1:在这一级别上,引进了资源的概念,服务端的每一个资源,都有一个对应的操作地址。
- Level2:在这一级别上,咱们引进了不同的 HTTP 恳求办法来描述不同的操作,例如 GET 表明查询、POST 表明插入、PUT 表明更新、DELETE 表明删去,并且运用 HTTP 的状态码来表明不同的呼应成果。一般来说,咱们在日常的接口开发中,基本上都能做到这一层级。可是这还不是最佳成果。
- Level3:依照 Leonard Richardson 的意思,这一层级的 REST 根据 HATEOAS(Hypertext As The Engine Of Application State),在这一级别上,除了回来资源的 JSON 之外,还会额定回来一组 Link,这组 Link 描述了关于该资源能够做哪些操作,以及具体的该怎么做。
在日常的开发中,咱们一般都是只完结到 Level2 这一层级,真正做到 Level3 的估量很少,不过虽然在工作中一般不会做到 Level3 这一层级,可是,我信任许多小伙伴应该是见过 Level3 层级的 REST 是啥样子的,特别是看过 vhr 视频的小伙伴,松哥在其间讲过,经过 Spring Data Jpa+Spring Rest Repositories 完结的 CURD 接口,其实便是一个达到了 Level3 层级的 REST。
2. Spring HATEOAS
那么接下来我先用 Spring HATEOAS 写一个简略的 REST,然后结合这个事例来和小伙伴们聊一聊究竟 Spring HATEOAS 有何不一样的当地。
首先咱们创立一个 Spring Boot 工程,引进 Web 和 Spring HATEOAS 依靠,如下:
创立好之后,咱们首先创立一个 User 实体类:
public class User extends RepresentationModel {
private Integer id;
private String username;
private String address;
//省略 getter/setter
}
留意这个 User 实体类需求承继自 RepresentationModel,以便利后续增加不同的 Link(以前旧的版别需求承继自 ResourceSupport)。
接下来写一个简略的测验接口。
查询一切用户:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping
public CollectionModel<User> list() {
List<User> list = new ArrayList<>();
User u1 = new User();
u1.setId(1);
u1.setUsername("javaboy");
u1.setAddress("www.javaboy.org");
u1.add(WebMvcLinkBuilder.linkTo(UserController.class).slash(u1.getId()).withSelfRel());
list.add(u1);
User u2 = new User();
u2.setId(2);
u2.setUsername("itboy");
u2.setAddress("www.itboyhub.com");
u2.add(WebMvcLinkBuilder.linkTo(UserController.class).slash(u2.getId()).withSelfRel());
list.add(u2);
CollectionModel<User> users = CollectionModel.of(list);
users.add(WebMvcLinkBuilder.linkTo(UserController.class).withRel("users"));
return users;
}
}
关于这个接口,我来说几点:
- 首先,关于这种回来一个调集或许数组的状况,回来的类型都是 CollectionModel。
- 把调集弄好之后(正常应该去数据库中查询,我这儿省劲直接创立了),经过
CollectionModel.of(list)
办法去获取一个CollectionModel<User>
目标。 - 关于每一个 user 目标,我都增加了一个 Link 目标,
WebMvcLinkBuilder.linkTo(UserController.class).slash(u1.getId()).withSelfRel()
表明生成当时目标的拜访链接。 -
WebMvcLinkBuilder.linkTo(UserController.class).withRel("users")
表明拜访一切数据的链接。
好了,这个接口写完之后,咱们拜访看下:
能够看到,回来的每一个 user 目标中,都有一个链接表明怎么独自拜访这个目标。最下面还有一个拜访一切目标的链接。
关于上面这个事例,或许有小伙伴会质疑,难道咱们从数据库中查询出来的 List 调集都要遍历一遍,然后给每一个 User 增加一个 Link 吗?其实不必,增加 Link 这个事能够直接在 User 类中完结,如下:
public class User extends RepresentationModel {
private Integer id;
private String username;
private String address;
public User(Integer id) {
super(WebMvcLinkBuilder.linkTo(UserController.class).slash(id).withSelfRel());
this.id = id;
}
//省略 getter/setter
}
能够看到,直接在结构办法中完结即可。此时接口里就不必那么杂乱了,如下:
@GetMapping
public CollectionModel<User> list() {
List<User> list = new ArrayList<>();
User u1 = new User(1);
u1.setUsername("javaboy");
u1.setAddress("www.javaboy.org");
list.add(u1);
User u2 = new User(2);
u2.setUsername("itboy");
u2.setAddress("www.itboyhub.com");
list.add(u2);
CollectionModel<User> users = CollectionModel.of(list);
users.add(WebMvcLinkBuilder.linkTo(UserController.class).withRel("users"));
return users;
}
那么关于根据 ID 来查询用户的需求,咱们也应该给一个接口如下:
@RestController
@RequestMapping("/users")
public class UserController {
@GetMapping("/{id}")
public EntityModel<User> getOne(@PathVariable Integer id) throws NoSuchMethodException {
User u = new User(id);
u.setUsername("javaboy");
u.setAddress("深圳");
u.add(Link.of("http://localhost:8080/users/"+id, "getOne"));
Link users = WebMvcLinkBuilder.linkTo(UserController.class).withRel("users");
u.add(users);
Link link = WebMvcLinkBuilder.linkTo(UserController.class).slash(u.getId()).withSelfRel();
u.add(link);
Method method = UserController.class.getMethod("getOne", Integer.class);
Link link2 = WebMvcLinkBuilder.linkTo(method, id).withSelfRel();
u.add(link2);
return EntityModel.of(u);
}
}
关于这个接口,我说如下几点:
- 假如回来类型是一个目标的话,需求运用
EntityModel<User>
类型。 - 搞好回来的目标之后,经过
EntityModel.of(u)
办法能够获取到目标数据类型。 - 这个当地,为了给小伙伴们演示不同的 Link 增加办法,我写了好多个(单纯为了演示不同的 Link 增加办法):
-
Link.of("http://localhost:8080/users/"+id, "getOne")
这种是自己纯手工去生成当时目标的拜访链接,很明显这不是一个很好的计划。当时目标的拜访链接建议运用上文中说到的办法。 -
WebMvcLinkBuilder.linkTo(UserController.class).withRel("users")
这个是生成当时这个 Controller 的拜访链接,一般便是拜访一切用户目标的链接。 -
WebMvcLinkBuilder.linkTo(UserController.class).slash(u.getId()).withSelfRel()
前文已经用过了,不多说了,实践应用中建议运用这种。 - 也能够根据某一个办法自动生成,像这样
WebMvcLinkBuilder.linkTo(method, id).withSelfRel()
,这个是生成某一个具体办法的拜访链接。
-
好了,现在咱们来看下这个接口生成的 JSON,如下:
生成的这段 JSON 我将之符号为了三部分:
- 榜首部分,self,便是本身的拜访链接,这三个链接别离是 User 的结构办法,以及前面说到的 3.3 和 3.4 的办法生成的。
- 第二部分,getOne 这个,是前面 3.1 中说到的办法生成的。
- 第三部分,users 这个,是前面说到的 3.2 办法生成的。
当然,其实这块还有许多其他的生成链接的玩法,可是我就不一一介绍了,小伙伴们能够参阅官方文档:
- docs.spring.io/spring-hate…
从上面 Spring HATEOAS 中回来的 JSON 咱们大致上能够看到它的特点:
当咱们运用了 Spring HATEOAS,此时,客户端就会经过服务端回来的 Link Rel 来获取恳求的 URI(假如没有运用 Spring HATEOAS,则客户端拜访的 URI 都是提前在客户端硬编码的),现在咱们就能够做到服务端在不破坏客户端完结的状况下动态的完结 URI 的修改,从而进一步解耦客户端和服务端。
简而言之,现在客户端能干什么事情,在服务端回来的 JSON 中都会告知客户端,客户端从服务端回来的 JSON 中获取到恳求的 URL,然后直接执行即可。假如这个恳求地址发生改变的话,客户端也会及时拿到最新的地址。
或许上面的比如小伙伴们感触还不是很明显,我再给咱们看一段 JSON:
{
"tracking_id": "666",
"status": "WAIT_PAYMENT",
"items": [
{
"name": "book",
"quantity": 1
}
],
"_Links": {
"self": {
"href": "http://localhost:8080/orders/666"
},
"cancel": {
"href": "http://localhost:8080/orders/666"
},
"payment": {
"href": "http://localhost:8080/orders/666/payments"
}
}
}
这是电商体系下单之后等待付出的过程中回来的 JSON,这儿的 links 给出了三个:
- self:拜访这个链接能够查看当时订单信息(GET 恳求)。
- cancel:拜访这个链接能够取消当时订单(DELETE 恳求)。
- payment:拜访这个链接能够付出当时订单(POST 恳求)。
这个比如就很直白了,便是在回来的 JSON 中,直接告知你接下来能做哪些操作,对应的 URL 别离是什么,前端拿到之后直接操作,假如这些操作路径发生了改变,前端也会立马拿到最新的路径。
这便是 Spring HATEOAS 的好处。总归一句话,Spring HATEOAS 发起在呼应回来的 Link 中给出对该资源接下来操作的 URL。这种办法解耦了服务端 URI,也能够让客户端开发者更容易地探索 API。
3. REST 的优缺陷
虽然咱们现在都鼓舞规划 REST 风格的 API,然而 REST 也不全是优点,事物总是具有两面性,REST 的优缺陷别离如下。
3.1 优点
- 首先,REST 足够简略,有必定 Web 开发经验的小伙伴都能够快速上手 REST。
- REST 风格的接口测验起来也十分便利,利用浏览器自带的一些 REST 插件或许是 POSTMAN 之类的东西,就能够十分便利的完结 REST 接口的测验。
- 不需求中心代理,简化了体系的结构。
- HTTP 对防火墙比较友爱。
3.2 缺陷
- REST 只支撑恳求-呼应的通讯办法,不支撑服务端推送音讯到客户端。
- 给恳求取一个合适的姓名比较困难,特别是有多个相类似的接口时,例如有多个增加接口、多个更新接口等。
- 因为没有中心代理,所以恳求/呼应的时候,服务端和客户端都必须在线。
好啦,跟小伙伴们聊了 REST 和 Spring HATEOAS,感兴趣的小伙伴能够去试试哦~