共计 9786 个字符,预计需要花费 25 分钟才能阅读完成。
书接上文⬆⬆⬆在 REST API 中构建链接到目前为止,您曾经应用根本链接构建了一个可进化的 API。为了倒退您的 API 并更好地为您的客户服务,您须要承受超媒体作为应用程序状态引擎的概念。这意味着什么?在本节中,您将具体探讨它。业务逻辑不可避免地会建设波及流程的规定。此类零碎的危险在于咱们常常将此类服务器端逻辑带入客户端并建设强耦合。REST 就是要突破这种连贯并最小化这种耦合。为了展现如何在不触发客户端中断更改的状况下应答状态变动,设想一下增加一个履行订单的零碎。第一步,定义一条 Order 记录:链接 /src/main/java/payroll/Order.javapackage payroll;import java.util.Objects;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.Table;@Entity@Table(name = “CUSTOMER_ORDER”)class Order {private @Id @GeneratedValue Long id; private String description; private Status status; Order() {} Order(String description, Status status) {this.description = description; this.status = status;} public Long getId() { return this.id;} public String getDescription() { return this.description;} public Status getStatus() { return this.status;} public void setId(Long id) {this.id = id;} public void setDescription(String description) {this.description = description;} public void setStatus(Status status) {this.status = status;} @Override public boolean equals(Object o) {if (this == o) return true; if (!(o instanceof Order)) return false; Order order = (Order) o; return Objects.equals(this.id, order.id) && Objects.equals(this.description, order.description) && this.status == order.status; } @Override public int hashCode() { return Objects.hash(this.id, this.description, this.status); } @Override public String toString() { return “Order{” + “id=” + this.id + “, description='” + this.description + ‘\” + “, status=” + this.status + ‘}’; }}复制该类须要 JPA@Table 正文将表的名称更改为,CUSTOMER_ORDER 因为 ORDER 它不是表的无效名称。它包含一个 description 字段以及一个 status 字段。从客户提交订单到实现或勾销订单时,订单必须经验一系列状态转换。这能够捕捉为 Java enum:链接 /src/main/java/payroll/Status.javapackage payroll;enum Status {IN_PROGRESS, // COMPLETED, // CANCELLED}复制这 enum 捕捉了一个 Order 能够占据的各种状态。对于本教程,让咱们放弃简略。要反对与数据库中的订单交互,必须定义相应的 Spring Data 存储库:Spring Data JPA 的 JpaRepository 根本接口 interface OrderRepository extends JpaRepository<Order, Long> {}复制有了这个,您当初能够定义一个根本的 OrderController:链接 /src/main/java/payroll/OrderController.java@RestControllerclass OrderController { private final OrderRepository orderRepository; private final OrderModelAssembler assembler; OrderController(OrderRepository orderRepository, OrderModelAssembler assembler) {this.orderRepository = orderRepository; this.assembler = assembler;} @GetMapping(“/orders”) CollectionModel<EntityModel<Order>> all() { List<EntityModel<Order>> orders = orderRepository.findAll().stream() // .map(assembler::toModel) // .collect(Collectors.toList()); return CollectionModel.of(orders, // linkTo(methodOn(OrderController.class).all()).withSelfRel()); } @GetMapping(“/orders/{id}”) EntityModel<Order> one(@PathVariable Long id) {Order order = orderRepository.findById(id) // .orElseThrow(() -> new OrderNotFoundException(id)); return assembler.toModel(order); } @PostMapping(“/orders”) ResponseEntity<EntityModel<Order>> newOrder(@RequestBody Order order) {order.setStatus(Status.IN_PROGRESS); Order newOrder = orderRepository.save(order); return ResponseEntity // .created(linkTo(methodOn(OrderController.class).one(newOrder.getId())).toUri()) // .body(assembler.toModel(newOrder)); }}复制它蕴含与您迄今为止构建的控制器雷同的 REST 控制器设置。它同时注入 OrderRepositorya 和 a (not yet built) OrderModelAssembler。前两个 Spring MVC 路由解决聚合根以及单个我的项目 Order 资源申请。第三条 Spring MVC 路由通过在 IN_PROGRESS 状态中启动它们来解决创立新订单。所有控制器办法都返回 Spring HATEOAS 的 RepresentationModel 子类之一以正确出现超媒体(或围绕此类类型的包装器)。在构建 之前 OrderModelAssembler,让咱们探讨须要产生的事件。您正在对、和 之间的状态流 Status.IN_PROGRESS 进行 Status.COMPLETED 建模 Status.CANCELLED。向客户端提供此类数据时,一件很天然的事件是让客户端依据此无效负载决定它能够做什么。但那是谬误的。当您在此流程中引入新状态时会产生什么?UI 上各种按钮的搁置可能是谬误的。如果您更改了每个州的名称,可能是在编码国内反对并显示每个州的区域设置特定文本时会怎么?这很可能会毁坏所有客户。输出 HATEOAS 或超媒体作为应用程序状态引擎。与其让客户端解析无效负载,不如为它们提供链接以收回无效操作的信号。将基于状态的操作与数据负载拆散。换句话说,当 CANCEL 和 COMPLETE 是无效操作时,将它们动静增加到链接列表中。客户端只须要在链接存在时向用户显示相应的按钮。这使客户端不用晓得此类操作何时无效,从而升高了服务器及其客户端在状态转换逻辑上不同步的危险。曾经承受了 Spring HATEOASRepresentationModelAssembler 组件的概念,将这样的逻辑放入其中 OrderModelAssembler 将是捕捉此业务规定的完满地位:链接 /src/main/java/payroll/OrderModelAssembler.javapackage payroll;import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.;import org.springframework.hateoas.EntityModel;import org.springframework.hateoas.server.RepresentationModelAssembler;import org.springframework.stereotype.Component;@Componentclass OrderModelAssembler implements RepresentationModelAssembler<Order, EntityModel<Order>> {@Override public EntityModel<Order> toModel(Order order) {// Unconditional links to single-item resource and aggregate root EntityModel<Order> orderModel = EntityModel.of(order, linkTo(methodOn(OrderController.class).one(order.getId())).withSelfRel(), linkTo(methodOn(OrderController.class).all()).withRel(“orders”)); // Conditional links based on state of the order if (order.getStatus() == Status.IN_PROGRESS) {orderModel.add(linkTo(methodOn(OrderController.class).cancel(order.getId())).withRel(“cancel”)); orderModel.add(linkTo(methodOn(OrderController.class).complete(order.getId())).withRel(“complete”)); } return orderModel; }}复制此资源组装器始终蕴含指向单项资源的本身链接以及返回聚合根的链接。但它也包含两个条件链接 OrderController.cancel(id)以及 OrderController.complete(id)(尚未定义)。这些链接仅在订单状态为 时显示 Status.IN_PROGRESS。如果客户能够采纳 HAL 和读取链接的能力,而不是简略地读取一般的旧 JSON 数据,他们能够替换对订单零碎畛域常识的需要。这天然缩小了客户端和服务器之间的耦合。它关上了调整订单履行流程的大门,而不会在流程中毁坏客户。要实现订单履行,请将以下内容增加到 OrderController 操作中 cancel:在 OrderController 中创立“勾销”操作 @DeleteMapping(“/orders/{id}/cancel”)ResponseEntity<?> cancel(@PathVariable Long id) {Order order = orderRepository.findById(id) // .orElseThrow(() -> new OrderNotFoundException(id)); if (order.getStatus() == Status.IN_PROGRESS) {order.setStatus(Status.CANCELLED); return ResponseEntity.ok(assembler.toModel(orderRepository.save(order))); } return ResponseEntity // .status(HttpStatus.METHOD_NOT_ALLOWED) // .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE) // .body(Problem.create() // .withTitle(“Method not allowed”) // .withDetail(“You can’t cancel an order that is in the ” + order.getStatus() + ” status”));}复制 Order 它在容许勾销之前查看状态。如果它不是一个无效的状态,它会返回一个 RFC-7807 Problem,一个反对超媒体的谬误容器。如果转换的确无效,则将 转换 Order 为 CANCELLED。并将其增加到 OrderController 订单实现中:在 OrderController 中创立“残缺”操作 @PutMapping(“/orders/{id}/complete”)ResponseEntity<?> complete(@PathVariable Long id) {Order order = orderRepository.findById(id) // .orElseThrow(() -> new OrderNotFoundException(id)); if (order.getStatus() == Status.IN_PROGRESS) {order.setStatus(Status.COMPLETED); return ResponseEntity.ok(assembler.toModel(orderRepository.save(order))); } return ResponseEntity // .status(HttpStatus.METHOD_NOT_ALLOWED) // .header(HttpHeaders.CONTENT_TYPE, MediaTypes.HTTP_PROBLEM_DETAILS_JSON_VALUE) // .body(Problem.create() // .withTitle(“Method not allowed”) // .withDetail(“You can’t complete an order that is in the ” + order.getStatus() + ” status”));}复制这实现了相似的逻辑以避免 Order 状态实现,除非处于正确的状态。让咱们更新 LoadDatabase 以预加载一些 Orders 以及 Employee 它之前加载的 s。更新数据库预加载器 package payroll;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.CommandLineRunner;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;@Configurationclass LoadDatabase {private static final Logger log = LoggerFactory.getLogger(LoadDatabase.class); @Bean CommandLineRunner initDatabase(EmployeeRepository employeeRepository, OrderRepository orderRepository) {return args -> { employeeRepository.save(new Employee(“Bilbo”, “Baggins”, “burglar”)); employeeRepository.save(new Employee(“Frodo”, “Baggins”, “thief”)); employeeRepository.findAll().forEach(employee -> log.info(“Preloaded ” + employee)); orderRepository.save(new Order(“MacBook Pro”, Status.COMPLETED)); orderRepository.save(new Order(“iPhone”, Status.IN_PROGRESS)); orderRepository.findAll().forEach(order -> { log.info(“Preloaded ” + order); }); }; }}复制当初你能够测试了!要应用新生成的订单服务,只需执行一些操作:$ curl -v http://localhost:8080/orders{“_嵌入”:{“订单”:[ {“身份证”:3,“形容”:“MacBook Pro”,“状态”:“已实现”,“_链接”:{“本人”:{ “href”: “http://localhost:8080/orders/3”},“订单”:{“href”: “http://localhost:8080/orders”} } }, {“身份证”:4,“形容”:“iPhone”,“状态”:“IN_PROGRESS”,“_链接”:{“本人”:{ “href”: “http://localhost:8080/orders/4”},“订单”:{“href”: “http://localhost:8080/orders”},“勾销”:{“href”: “http://localhost:8080/orders/4/cancel”},“齐全的”:{“href”: “http://localhost:8080/orders/4/complete”} } } ] },“_链接”:{“本人”:{ “href”: “http://localhost:8080/orders”} }}此 HAL 文档会依据其以后状态立刻显示每个订单的不同链接。第一个订单,即 COMPLETED 只有导航链接。未显示状态转换链接。第二个订单,即 IN_PROGRESS 还具备勾销链接和残缺链接。尝试勾销订单:$ curl -v -X 删除 http://localhost:8080/orders/…; 删除 /orders/4/cancel HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 承受:/>< HTTP/1.1 200< 内容类型:application/hal+json;charset=UTF-8< 传输编码:分块 < 日期:2018 年 8 月 27 日星期一 15:02:10 GMT<{“身份证”:4,“形容”:“iPhone”,“状态”:“勾销”,“_链接”:{“本人”:{ “href”: “http://localhost:8080/orders/4”},“订单”:{“href”: “http://localhost:8080/orders”} }}此响应显示一个 HTTP 200 状态代码,表明它是胜利的。响应 HAL 文档显示该订单处于新状态 (CANCELLED)。扭转状态的链接隐没了。如果再次尝试雷同的操作……$ curl -v -X 删除 http://localhost:8080/orders/4/cancel* TCP_NODELAY 设置 * 连贯到 localhost (::1) 端口 8080 (#0)> 删除 /orders/4/cancel HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 承受:*/*>< HTTP/1.1 405< 内容类型:应用程序 / 问题 +json< 传输编码:分块 < 日期:2018 年 8 月 27 日星期一 15:03:24 GMT<{“title”: “ 办法不容许 ”, “detail”: “ 您不能取消处于 CANCELED 状态的订单 ”}…您会看到 HTTP 405 Method Not Allowed 响应。DELETE 已成为有效操作。Problem 响应对象分明地表明您不能“勾销”曾经处于“CANCELLED”状态的订单。此外,尝试实现雷同的订单也会失败:$ curl -v -X PUT localhost:8080/orders/4/complete TCP_NODELAY 设置 连贯到 localhost (::1) 端口 8080 (#0)> PUT /orders/4/ 实现 HTTP/1.1> 主机:本地主机:8080> 用户代理:curl/7.54.0> 承受:/*>< HTTP/1.1 405< 内容类型:应用程序 / 问题 +json< 传输编码:分块 < 日期:2018 年 8 月 27 日星期一 15:05:40 GMT<{“title”: “ 办法不容许 ”, “detail”: “ 您无奈实现处于 CANCELED 状态的订单 ”} 有了这所有,您的订单履行服务就可能有条件地显示可用的操作。它还能够避免有效操作。通过利用超媒体和链接协定,客户端能够构建得更坚硬,并且不太可能仅仅因为数据的变动而解体。Spring HATEOAS 能够轻松构建您须要为客户提供服务的超媒体。概括在本教程中,您应用了各种策略来构建 REST API。事实证明,REST 不仅仅是丑陋的 URI 和返回 JSON 而不是 XML。相同,以下策略有助于升高您的服务毁坏您可能管制或可能无法控制的现有客户的可能性:不要删除旧字段。相同,反对他们。应用基于 rel 的链接,这样客户端就不用放心 URI 进行硬编码。尽可能长时间地保留旧链接。即便您必须更改 URI,也要保留 rels,以便旧客户端能够应用新性能。当各种状态驱动操作可用时,应用链接而不是无效负载数据来批示客户端。RepresentationModelAssembler 为每种资源类型构建实现并在所有控制器中应用这些组件仿佛须要一些致力。然而这种额定的服务器端设置(感激 Spring HATEOAS 使之变得容易)能够确保您管制的客户端(更重要的是,您不管制的客户端)能够随着您的 API 随着倒退而轻松升级。咱们对于如何应用 Spring 构建 RESTful 服务员的教程到此结束。本教程的每个局部都在单个 github 存储库中作为独自的子项目进行治理:nonrest — 没有自媒体的简略 Spring MVC 应用程序 rest — Spring MVC + Spring HATEOAS 应用程序,每个资源的 HAL 示意进化 - REST 应用程序,其中一个字段已进化但保留旧数据以实现向后兼容性链接 - REST 应用程序,其中条件链接用于向客户端收回无效状态更改信号要查看应用 Spring HATEOAS 的更多示例,请参阅 Spring 中国教育管理中心 #java##spring 认证## 程序员##spring#
以上就是明天对于 Spring 的一些探讨,对你有帮忙吗?如果你有趣味深刻理解,欢送到 Spring 中国教育管理中心留言交换!