0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何使用Spring构建REST服务(四)

「Spring」认证安全架构 来源:「Spring」认证安全架构 作者:「Spring」认证安全 2022-07-28 16:02 次阅读

书接上文⬆⬆⬆

不断发展的 REST API

通过一个额外的库和几行额外的代码,您已将超媒体添加到您的应用程序中。但这并不是使您的服务成为 RESTful 所需的唯一事情。REST 的一个重要方面是它既不是技术堆栈也不是单一标准。

REST 是架构约束的集合,采用这些约束会使您的应用程序更具弹性。弹性的一个关键因素是,当您对服务进行升级时,您的客户不会遭受停机时间的困扰。

在“过去”的日子里,升级因破坏客户端而臭名昭著。换句话说,升级到服务器需要更新客户端。在当今时代,花费数小时甚至数分钟进行升级的停机时间可能会造成数百万美元的收入损失。

有些公司要求您向管理层提出一个计划,以尽量减少停机时间。过去,您可以在周日凌晨 2:00 进行升级,此时负载最低。但在今天的基于互联网的电子商务中,国际客户在其他时区,这样的策略就没有那么有效了。

基于 SOAP 的服务和基于CORBA 的服务非常脆弱。很难推出可以同时支持新旧客户端的服务器。使用基于 REST 的实践,这要容易得多。特别是使用 Spring 堆栈。

支持对 API 的更改

想象一下这个设计问题:您已经推出了一个具有Employee基于此记录的系统。该系统大受欢迎。你已经把你的系统卖给了无数的企业。突然,需要拆分员工的姓名firstNamelastName出现。

哦哦。没想到。

在您打开课程并用andEmployee替换单个字段之前,请停下来想一想。这会破坏任何客户吗?升级它们需要多长时间。您甚至控制所有访问您服务的客户端吗?namefirstNamelastName

停机时间 = 损失金钱。管理层准备好了吗?

有一个比 REST 早几年的旧策略。

永远不要删除数据库中的列。

— 未知

您始终可以将列(字段)添加到数据库表中。但不要带走一个。RESTful 服务中的原理是相同的。

将新字段添加到您的 JSON 表示中,但不要带走任何字段。像这样:

支持多个客户端的 JSON

{  "id": 1,  "firstName": "Bilbo",  "lastName": "Baggins",  "role": "burglar",  "name": "Bilbo Baggins",  "_links": {    "self": {      "href": "http://localhost:8080/employees/1"    },    "employees": {      "href": "http://localhost:8080/employees"    }  }}

请注意此格式如何显示firstName, lastName, AND name?虽然它包含重复信息,但其目的是同时支持新老客户。这意味着您可以升级服务器,而无需同时升级客户端。一个可以减少停机时间的好举措。

您不仅应该以“旧方式”和“新方式”显示这些信息,还应该以两种方式处理传入的数据。

如何?简单的。像这样:

处理“旧”和“新”客户的员工记录

package payroll;import java.util.Objects;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;@Entityclass Employee {  private @Id @GeneratedValue Long id;  private String firstName;  private String lastName;  private String role;  Employee() {}  Employee(String firstName, String lastName, String role) {    this.firstName = firstName;    this.lastName = lastName;    this.role = role;  }  public String getName() {    return this.firstName + " " + this.lastName;  }  public void setName(String name) {    String[] parts = name.split(" ");    this.firstName = parts[0];    this.lastName = parts[1];  }  public Long getId() {    return this.id;  }  public String getFirstName() {    return this.firstName;  }  public String getLastName() {    return this.lastName;  }  public String getRole() {    return this.role;  }  public void setId(Long id) {    this.id = id;  }  public void setFirstName(String firstName) {    this.firstName = firstName;  }  public void setLastName(String lastName) {    this.lastName = lastName;  }  public void setRole(String role) {    this.role = role;  }  @Override  public boolean equals(Object o) {    if (this == o)      return true;    if (!(o instanceof Employee))      return false;    Employee employee = (Employee) o;    return Objects.equals(this.id, employee.id) && Objects.equals(this.firstName, employee.firstName)        && Objects.equals(this.lastName, employee.lastName) && Objects.equals(this.role, employee.role);  }  @Override  public int hashCode() {    return Objects.hash(this.id, this.firstName, this.lastName, this.role);  }  @Override  public String toString() {    return "Employee{" + "id=" + this.id + ", firstName='" + this.firstName + '\'' + ", lastName='" + this.lastName        + '\'' + ", role='" + this.role + '\'' + '}';  }}

这个类与以前版本的Employee. 让我们回顾一下变化:

  • 字段name已替换为firstNamelastName
  • 定义了旧name属性的“虚拟”吸气剂。getName()它使用firstNameandlastName字段来产生一个值。
  • name还定义了旧属性的“虚拟”设置器, setName(). 它解析传入的字符串并将其存储到适当的字段中。

当然,并非对 API 的每一次更改都像拆分字符串或合并两个字符串一样简单。但是对于大多数场景来说,想出一组转换肯定不是不可能的,对吧?

不要忘记更改预加载数据库的方式(在 中LoadDatabase)以使用这个新的构造函数。

log.info("Preloading " + repository.save(new Employee("Bilbo", "Baggins", "burglar")));

log.info("Preloading " + repository.save(new Employee("Frodo", "Baggins", "thief")));

适当的反应

朝着正确方向迈出的另一个步骤是确保您的每个 REST 方法都返回正确的响应。像这样更新 POST 方法:

处理“旧”和“新”客户端请求的 POST

@PostMapping("/employees")ResponseEntity newEmployee(@RequestBody Employee newEmployee) {  EntityModel entityModel = assembler.toModel(repository.save(newEmployee));  return ResponseEntity //      .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) //      .body(entityModel);}复制
  • Employee对象像以前一样保存。但是生成的对象使用EmployeeModelAssembler.
  • Spring MVCResponseEntity用于创建HTTP 201 Created状态消息。这种类型的响应通常包含一个Location响应头,我们使用从模型的自相关链接派生的 URI。
  • 此外,返回已保存对象的基于模型的版本。

通过这些调整,您可以使用相同的端点来创建新的员工资源,并使用遗留name字段:

$ curl -v -X POST localhost:8080/employees -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "gardener"}'

输出如下所示:

> POST /员工 HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 接受:*/*> 内容类型:应用程序/json> 内容长度:46>< 位置:http://localhost:8080/employees/3< 内容类型:application/hal+json;charset=UTF-8< 传输编码:分块< 日期:格林威治标准时间 2018 年 8 月 10 日星期五 19:44:43<{  “身份证”:3,  "firstName": "Samwise",  "lastName": "Gamgee",  “角色”:“园丁”,  "name": "Samwise Gamgee",  “_链接”:{    “自己”: {      “href”:“http://localhost:8080/employees/3”    },    “雇员”: {      "href": "http://localhost:8080/employees"    }  }}

这不仅使生成的对象在 HAL(name以及firstName/ lastName)中呈现,而且Location标头也填充了
http://localhost:8080/employees/3. 超媒体驱动的客户端可以选择“冲浪”到这个新资源并继续与之交互。

PUT 控制器方法需要类似的调整:

为不同的客户端处理 PUT

@PutMapping("/employees/{id}")ResponseEntity replaceEmployee(@RequestBody Employee newEmployee, @PathVariable Long id) {  Employee updatedEmployee = repository.findById(id) //      .map(employee -> {        employee.setName(newEmployee.getName());        employee.setRole(newEmployee.getRole());        return repository.save(employee);      }) //      .orElseGet(() -> {        newEmployee.setId(id);        return repository.save(newEmployee);      });  EntityModel entityModel = assembler.toModel(updatedEmployee);  return ResponseEntity //      .created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) //      .body(entityModel);}复制

然后使用 将操作Employee构建的对象包装到一个对象中。使用该方法,您可以检索由rel创建的。这个方法返回一个必须用方法变成一个的。save()
EmployeeModelAssemblerEntityModelgetRequiredLink()LinkEmployeeModelAssemblerSELFLinkURItoUri

由于我们想要一个比200 OK更详细的 HTTP 响应代码,我们将使用 Spring MVC 的ResponseEntity包装器。它有一个方便的静态方法created(),我们可以在其中插入资源的 URI。HTTP 201 Created是否具有正确的语义值得商榷,因为我们不一定要“创建”新资源。但它预装了一个Location响应头,所以用它运行。

$ curl -v -X PUT localhost:8080/employees/3 -H 'Content-Type:application/json' -d '{"name": "Samwise Gamgee", "role": "ring bearer"}'* TCP_NODELAY 设置* 连接到 localhost (::1) 端口 8080 (#0)> PUT /employees/3 HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 接受:*/*> 内容类型:应用程序/json> 内容长度:49>< HTTP/1.1 201< 位置:http://localhost:8080/employees/3< 内容类型:application/hal+json;charset=UTF-8< 传输编码:分块< 日期:格林威治标准时间 2018 年 8 月 10 日星期五 19:52:56{“身份证”:3,"firstName": "Samwise","lastName": "Gamgee",“角色”:“戒指持有者”,"name": "Samwise Gamgee",“_链接”:{“自己”: {“href”:“http://localhost:8080/employees/3”},“雇员”: {"href": "http://localhost:8080/employees"}}}

该员工资源现已更新,并且位置 URI 已发回。最后,适当地更新 DELETE 操作:

处理 DELETE 请求

@DeleteMapping("/employees/{id}")ResponseEntity deleteEmployee(@PathVariable Long id) {  repository.deleteById(id);  return ResponseEntity.noContent().build();}复制

这将返回HTTP 204 No Content响应。

$ curl -v -X 删除本地主机:8080/employees/1* TCP_NODELAY 设置* 连接到 localhost (::1) 端口 8080 (#0)> 删除 /employees/1 HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 接受:*/*>< HTTP/1.1 204< 日期:格林威治标准时间 2018 年 8 月 10 日星期五 21:30:26

对类中的字段进行更改Employee需要与您的数据库团队协调,以便他们可以正确地将现有内容迁移到新列中。

您现在已准备好进行升级,不会干扰现有客户端,而新客户端可以利用这些增强功能!

顺便说一句,您是否担心通过网络发送太多信息?在某些每个字节都很重要的系统中,API 的发展可能需要退居二线。但是在你测量之前不要追求这种过早的优化。


以上就是今天关于Spring的一些讨论,对你有帮助吗?如果你有兴趣深入了解,欢迎到Spring中国教育管理中心留言交流!

审核编辑:汤梓红

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • spring
    +关注

    关注

    0

    文章

    332

    浏览量

    14161
  • REST
    +关注

    关注

    0

    文章

    32

    浏览量

    9358
收藏 人收藏

    评论

    相关推荐

    什么是java spring

    的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。Spring是一个容器,它包含并且管理系统对象
    发表于 09-11 11:16

    如何用ACM简化你的Spring Cloud微服务环境配置管理

    摘要: 本文我们就如何使用阿里云ACM这样的配置管理产品在Spring Cloud中替代Spring Cloud Config帮助简化环境配置管理做一个简单的示例,帮助你理解基于ACM来简化微服务
    发表于 02-02 14:18

    EDAS再升级!全面支持Spring Cloud应用

    摘要: 近日,阿里中间件(Aliware)的企业级分布式应用服务EDAS宣布再次升级,全面支持Spring Cloud应用。点此查看原文:[url=]http://click.aliyun.com
    发表于 02-02 15:20

    使用阿里云ACM简化你的Spring Cloud微服务环境配置管理

    摘要: 本文我们就如何使用阿里云ACM这样的配置管理产品在Spring Cloud中替代Spring Cloud Config帮助简化环境配置管理做一个简单的示例,帮助你理解基于ACM来简化微服务
    发表于 07-04 17:16

    Spring Boot嵌入式Web容器原理是什么

    Spring Boot嵌入式Web容器原理Spring Boot的目标是构建“非常容易创建、独立、产品级别的基于Spring的应用”。这些应用是“立即可运行的”。在这个过程中,完全没有
    发表于 12-16 07:57

    REST端口支持构建动态REST请求来使用RESTful API网络

    REST端口支持构建动态REST请求来使用RESTful API网络服务。 概览 REST端口暴露了一个简单的接口来为
    的头像 发表于 01-17 09:11 4393次阅读

    REST API是什么,如何使用REST端口

    /服务器) 模型对资源进行增删改查操作。而其中客户端和服务器是分离的,而知行之桥中的REST端口就是作为REST API中的客户端,对服务
    的头像 发表于 02-17 18:00 7879次阅读
    <b class='flag-5'>REST</b> API是什么,如何使用<b class='flag-5'>REST</b>端口

    Spring REST Docs RESTful服务文档

    ./oschina_soft/spring-restdocs.zip
    发表于 05-24 09:31 1次下载
    <b class='flag-5'>Spring</b> <b class='flag-5'>REST</b> Docs RESTful<b class='flag-5'>服务</b>文档

    Spring认证是什么?

    ,例如:配置、组件扫描、AOP、数据访问和事务、REST、安全、自动配置、执行器、 Spring boot测试等。
    的头像 发表于 07-04 10:19 1080次阅读
    <b class='flag-5'>Spring</b>认证是什么?

    如何使用Spring构建REST服务(一)

    关于 REST 如何适应微服务世界还有一个更大的讨论,但是——对于本教程——让我们看看构建 RESTful 服务
    的头像 发表于 07-28 15:59 701次阅读

    如何使用Spring构建REST服务(二)

    要使用 Web 层次包装您的存储库,您必须使用 Spring MVC。多亏了 Spring Boot,代码基础设施很少。相反,我们可以专注于行动。
    的头像 发表于 07-28 16:00 550次阅读

    如何使用Spring构建REST服务(三)

    到目前为止,您拥有一个基于 Web 服务来处理涉及员工数据的核心操作。但这还不足以让事情变得“RESTful”。
    的头像 发表于 07-28 16:01 589次阅读

    如何使用Spring构建REST服务(五)

    到目前为止,您已经使用基本链接构建了一个可进化的 API。为了发展您的 API 并更好地为您的客户服务,您需要接受超媒体作为应用程序状态引擎的概念。
    的头像 发表于 07-28 16:03 594次阅读

    REST的6大指导原则

    systems )架构风格。由Roy Fielding 提出。 REST API 也称RESTful API, 其遵循REST架构规范的应用编程接口, 支持与RESTful WEB服务进行交互。简单来讲就是
    的头像 发表于 10-09 14:27 814次阅读

    Spring Cloud :打造可扩展的微服务网关

    Spring Cloud Gateway是一个基于Spring Framework 5和Project Reactor的反应式编程模型的微服务网关。它提供了丰富的功能,包括动态路由、请求限流、集成安全性等,使其成为
    的头像 发表于 10-22 10:03 260次阅读
    <b class='flag-5'>Spring</b> Cloud :打造可扩展的微<b class='flag-5'>服务</b>网关