An Intro to Spring HATEOAS – SpringHATEOAS介绍

最后修改: 2016年 4月 21日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

This article explains the process of creating hypermedia-driven REST web service using the Spring HATEOAS project.

本文介绍了使用Spring HATEOAS项目创建超媒体驱动的REST Web服务的过程。

2. Spring-HATEOAS

2.Spring-HATEOAS

The Spring HATEOAS project is a library of APIs that we can use to easily create REST representations that follow the principle of HATEOAS (Hypertext as the Engine of Application State).

Spring HATEOAS项目是一个API库,我们可以用它来轻松创建遵循HATEOAS(超文本作为应用状态的引擎)原则的REST表示。

Generally speaking, the principle implies that the API should guide the client through the application by returning relevant information about the next potential steps, along with each response.

一般而言,该原则意味着API应通过在每个响应中返回有关下一个潜在步骤的相关信息来引导客户完成应用程序。

In this article, we’re going to build an example using Spring HATEOAS with the goal of decoupling the client and server, and theoretically allowing the API to change its URI scheme without breaking clients.

在这篇文章中,我们将使用Spring HATEOAS构建一个例子,目的是将客户端和服务器解耦,理论上允许API改变其URI方案而不破坏客户端。

3. Preparation

3.准备工作

First, let’s add the Spring HATEOAS dependency:

首先,让我们添加Spring HATEOAS的依赖关系。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-hateoas</artifactId>
    <version>2.6.4</version>
</dependency>

If we’re not using Spring Boot we can add the following libraries to our project:

如果我们不使用Spring Boot,我们可以在我们的项目中添加以下库。

<dependency>
    <groupId>org.springframework.hateoas</groupId>
    <artifactId>spring-hateoas</artifactId>
    <version>1.4.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.plugin</groupId>
    <artifactId>spring-plugin-core</artifactId>
    <version>1.2.0.RELEASE</version>
</dependency>

As always, we can search the latest versions of the starter HATEOAS, the spring-hateoas and the spring-plugin-core dependencies in Maven Central.

像往常一样,我们可以在Maven中心搜索starter HATEOASspring-hateoasspring-plugin-core>依赖的最新版本。

Next, we have the Customer resource without Spring HATEOAS support:

接下来,我们有一个没有Spring HATEOAS支持的Customer资源。

public class Customer {

    private String customerId;
    private String customerName;
    private String companyName;

    // standard getters and setters
}

And we have a controller class without Spring HATEOAS support:

而我们有一个没有Spring HATEOAS支持的控制器类。

@RestController
@RequestMapping(value = "/customers")
public class CustomerController {
    @Autowired
    private CustomerService customerService;

    @GetMapping("/{customerId}")
    public Customer getCustomerById(@PathVariable String customerId) {
        return customerService.getCustomerDetail(customerId);
    }
}

Finally, the Customer resource representation:

最后,客户资源表示。

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company"
}

4. Adding HATEOAS Support

4.添加HATEOAS支持

In a Spring HATEOAS project, we don’t need to either look up the Servlet context nor concatenate the path variable to the base URI.

在Spring HATEOAS项目中,我们既不需要查找Servlet上下文,也不需要将路径变量与基础URI连接起来。

Instead, Spring HATEOAS offers three abstractions for creating the URI – RepresentationModel, Link, and WebMvcLinkBuilder. We can use these to create the metadata and associate it to the resource representation.

相反,Spring HATEOAS为创建URI提供了三个抽象–RepresentationModel、Link和WebMvcLinkBuilder。我们可以使用这些来创建元数据,并将其与资源表示关联起来。

4.1. Adding Hypermedia Support to a Resource

4.1.为资源添加超媒体支持

The project provides a base class called RepresentationModel to inherit from when creating a resource representation:

该项目提供了一个名为RepresentationModel的基类,以便在创建资源表示时继承。

public class Customer extends RepresentationModel<Customer> {
    private String customerId;
    private String customerName;
    private String companyName;
 
    // standard getters and setters
}

The Customer resource extends from the RepresentationModel class to inherit the add() method. So once we create a link, we can easily set that value to the resource representation without adding any new fields to it.

Customer资源从RepresentationModel类中扩展出来,继承了add()方法。因此,一旦我们创建了一个链接,我们就可以很容易地将该值设置到资源表示中,而无需向其添加任何新的字段。

4.2. Creating Links

4.2.创建链接

Spring HATEOAS provides a Link object to store the metadata (location or URI of the resource).

Spring HATEOAS提供一个Link对象来存储元数据(资源的位置或URI)。

First, we’ll create a simple link manually:

首先,我们将手动创建一个简单的链接。

Link link = new Link("http://localhost:8080/spring-security-rest/api/customers/10A");

The Link object follows the Atom link syntax and consists of a rel which identifies relation to the resource and href attribute which is the actual link itself.

Link对象遵循Atom链接语法,由一个rel组成,它标识了与资源的关系,href属性是实际链接本身。

Here’s how the Customer resource looks now that it contains the new link:

下面是Customer资源现在的样子,它包含新的链接。

{
    "customerId": "10A",
    "customerName": "Jane",
    "customerCompany": "ABC Company",
    "_links":{
        "self":{
            "href":"http://localhost:8080/spring-security-rest/api/customers/10A"
         }
    }
}

The URI associated with the response is qualified as a self link. The semantics of the self relation is clear – it’s simply the canonical location the resource can be accessed at.

与响应相关的URI被限定为一个self链接。self关系的语义很清楚–它只是资源可以被访问的标准位置。

4.3. Creating Better Links

4.3.创建更好的链接

Another very important abstraction offered by the library is the WebMvcLinkBuilder – which simplifies building URIs by avoiding hard-coded the links.

该库提供的另一个非常重要的抽象是WebMvcLinkBuilder–它通过避免硬编码的链接,简化了URI的构建

The following snippet shows building the customer self-link using the WebMvcLinkBuilder class:

下面的片段显示了使用WebMvcLinkBuilder类构建客户自我链接。

linkTo(CustomerController.class).slash(customer.getCustomerId()).withSelfRel();

Let’s have a look:

让我们看一看。

  • the linkTo() method inspects the controller class and obtains its root mapping
  • the slash() method adds the customerId value as the path variable of the link
  • finally, the withSelfMethod() qualifies the relation as a self-link

5. Relations

5.关系

In the previous section, we’ve shown a self-referencing relation. However, more complex systems may involve other relations as well.

在上一节中,我们展示了一个自我参照的关系。然而,更复杂的系统也可能涉及其他关系。

For example, a customer can have a relationship with orders. Let’s model the Order class as a resource as well:

例如,一个客户可以与订单有关系。让我们把Order类也建模为一种资源。

public class Order extends RepresentationModel<Order> {
    private String orderId;
    private double price;
    private int quantity;

    // standard getters and setters
}

At this point, we can extend the CustomerController with a method that returns all orders of a particular customer:

在这一点上,我们可以扩展CustomerController,用一个方法来返回某个特定客户的所有订单。

@GetMapping(value = "/{customerId}/orders", produces = { "application/hal+json" })
public CollectionModel<Order> getOrdersForCustomer(@PathVariable final String customerId) {
    List<Order> orders = orderService.getAllOrdersForCustomer(customerId);
    for (final Order order : orders) {
        Link selfLink = linkTo(methodOn(CustomerController.class)
          .getOrderById(customerId, order.getOrderId())).withSelfRel();
        order.add(selfLink);
    }
 
    Link link = linkTo(methodOn(CustomerController.class)
      .getOrdersForCustomer(customerId)).withSelfRel();
    CollectionModel<Order> result = CollectionModel.of(orders, link);
    return result;
}

Our method returns a CollectionModel object to comply with the HAL return type, as well as a “_self” link for each of the orders and the full list.

我们的方法返回一个CollectionModel对象以符合HAL的返回类型,以及每个订单和完整列表的”_self”链接。

An important thing to notice here is that the hyperlink for the customer orders depends on the mapping of getOrdersForCustomer() method. We’ll refer to these types of links as method links and show how the WebMvcLinkBuilder can assist in their creation.

这里需要注意的一个重要问题是,客户订单的超链接取决于getOrdersForCustomer()方法的映射。我们将把这些类型的链接称为方法链接,并展示WebMvcLinkBuilder如何协助创建它们。

6. Links to Controller Methods

6.控制器方法的链接

The WebMvcLinkBuilder offers rich support for Spring MVC Controllers. The following example shows how to build HATEOAS hyperlinks based on the getOrdersForCustomer() method of the CustomerController class:

WebMvcLinkBuilder为Spring MVC控制器提供丰富的支持。下面的例子展示了如何基于CustomerController类的getOrdersForCustomer()方法来构建HATEOAS超链接。

Link ordersLink = linkTo(methodOn(CustomerController.class)
  .getOrdersForCustomer(customerId)).withRel("allOrders");

The methodOn() obtains the method mapping by making dummy invocation of the target method on the proxy controller and sets the customerId as the path variable of the URI.

methodOn()通过对代理控制器的目标方法进行假调用获得方法映射,并将customerId作为URI的路径变量。

7. Spring HATEOAS in Action

7.SpringHATEOAS在行动

Let’s put the self-link and method link creation all together in a getAllCustomers() method:

让我们把自我链接和方法链接的创建都放在一个getAllCustomers()方法中:

@GetMapping(produces = { "application/hal+json" })
public CollectionModel<Customer> getAllCustomers() {
    List<Customer> allCustomers = customerService.allCustomers();

    for (Customer customer : allCustomers) {
        String customerId = customer.getCustomerId();
        Link selfLink = linkTo(CustomerController.class).slash(customerId).withSelfRel();
        customer.add(selfLink);
        if (orderService.getAllOrdersForCustomer(customerId).size() > 0) {
            Link ordersLink = linkTo(methodOn(CustomerController.class)
              .getOrdersForCustomer(customerId)).withRel("allOrders");
            customer.add(ordersLink);
        }
    }

    Link link = linkTo(CustomerController.class).withSelfRel();
    CollectionModel<Customer> result = CollectionModel.of(allCustomers, link);
    return result;
}

Next, let’s invoke the getAllCustomers() method:

接下来,让我们调用getAllCustomers()方法。

curl http://localhost:8080/spring-security-rest/api/customers

And examine the result:

并检查其结果。

{
  "_embedded": {
    "customerList": [{
        "customerId": "10A",
        "customerName": "Jane",
        "companyName": "ABC Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
          }
        }
      },{
        "customerId": "20B",
        "customerName": "Bob",
        "companyName": "XYZ Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B"
          },
          "allOrders": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/20B/orders"
          }
        }
      },{
        "customerId": "30C",
        "customerName": "Tim",
        "companyName": "CKV Company",
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/30C"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers"
    }
  }
}

Within each resource representation, there is a self link and the allOrders link to extract all orders of a customer. If a customer doesn’t have orders, then the link for orders won’t appear.

在每个资源代表中,有一个self链接和allOrders链接来提取客户的所有订单。如果一个客户没有订单,那么订单的链接就不会出现。

This example demonstrates how Spring HATEOAS fosters API discoverability in a rest web service. If the link exists, the client can follow it and get all orders for a customer:

这个例子展示了Spring HATEOAS如何在休息网络服务中促进API的可发现性。如果该链接存在,客户可以跟随它并获得客户的所有订单:

curl http://localhost:8080/spring-security-rest/api/customers/10A/orders
{
  "_embedded": {
    "orderList": [{
        "orderId": "001A",
        "price": 150,
        "quantity": 25,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/001A"
          }
        }
      },{
        "orderId": "002A",
        "price": 250,
        "quantity": 15,
        "_links": {
          "self": {
            "href": "http://localhost:8080/spring-security-rest/api/customers/10A/002A"
          }
        }
      }]
  },
  "_links": {
    "self": {
      "href": "http://localhost:8080/spring-security-rest/api/customers/10A/orders"
    }
  }
}

8. Conclusion

8.结论

In this tutorial, we’ve discussed how to build a hypermedia-driven Spring REST web service using the Spring HATEOAS project.

在本教程中,我们讨论了如何使用Spring HATEOAS项目建立一个超媒体驱动的Spring REST Web服务。

In the example, we see that the client can have a single entry point to the application and further actions can be taken based on the metadata in the response representation.

在这个例子中,我们看到客户端可以有一个进入应用程序的单一入口点,并且可以根据响应表示中的元数据采取进一步的行动。

This allows the server to change its URI scheme without breaking the client. Also, the application can advertise new capabilities by putting new links or URIs in the representation.

这允许服务器改变其URI方案而不破坏客户端。另外,应用程序可以通过在表示中放入新的链接或URI来宣传新的能力。

Finally, the full implementation of this article can be found in the GitHub project.

最后,本文的完整实现可以在GitHub项目中找到。