Spring HATEOAS 入門

REST,Spring
Remote
1
04:23 PM · Dec 01 ,2025

1. 概述

本文檔解釋了使用 Spring HATEOAS 項目創建基於超媒體的 REST Web 服務的過程。

2. Spring-HATEOAS

Spring HATEOAS 項目是一個庫,我們可以使用它輕鬆創建遵循 HATEOAS (Hypertext as the Engine of Application State) 原則的 REST 表示。

總的來説,這個原則意味着 API 應該通過返回有關下一步潛在步驟的相關信息,以及每個響應來引導客户端。

在本文中,我們將使用 Spring HATEOAS 構建一個示例,目標是解耦客户端和服務器,從而理論上允許 API 在不破壞客户端的情況下更改 URI 方案。

3. 準備工作

首先,添加 Spring HATEOAS 依賴項:

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

如果未使用 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>

如往常一樣,可以搜索最新版本的 HATEOAS 啓動器spring-hateoasspring-plugin-core 依賴項在 Maven Central。

接下來,我們有未帶 Spring HATEOAS 支持的 Customer 資源:

public class Customer {

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

    // standard getters and setters
}

並且我們有一個沒有 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);
    }
}

最後,Customer 資源的表示形式:

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

4. Adding HATEOAS Support

在 Spring HATEOAS 項目中,我們不需要查找 Servlet 上下文,也不需要將路徑變量與基本 URI 連接起來。

相反,Spring HATEOAS 提供三種抽象用於創建 URI – RepresentationModel, Link, and WebMvcLinkBuilder。我們可以使用這些來創建元數據並將其與資源表示形式關聯。

4.1. Adding Hypermedia Support to a Resource

該項目提供了一個名為 RepresentationModel 的基礎類,用於從創建資源表示形式時繼承。

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

Customer 資源從 RepresentationModel 類繼承,以繼承 add() 方法,以便在創建鏈接後,我們可以輕鬆地將該值設置為資源表示形式,而無需添加任何新的字段。

4.2. Creating Links

Spring HATEOAS 提供一個 Link 對象來存儲元數據(資源的定位或 URI)

首先,我們將創建一個簡單的鏈接:

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

Link 對象遵循 Atom 鏈接語法,包含一個 rel 屬性,用於標識與資源的關聯關係,以及 href 屬性,用於指定實際的鏈接本身。

以下是 Customer 資源現在包含新鏈接後的外觀:

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

與響應關聯的 URI 被標記為 self 鏈接。 self 關係的語義是明確的——它只是資源可以訪問的規範位置。

4.3. Creating Better Links

該庫提供的另一個非常重要的抽象是 WebMvcLinkBuilder – 它可以簡化構建 URI 的過程,避免硬編碼鏈接

以下代碼片段演示了使用 WebMvcLinkBuilder 類構建客户的自鏈接:

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

讓我們看看:

  • linkTo() 方法檢查控制器類並獲取其根映射
  • slash() 方法將 customerId 值作為鏈接的路徑變量添加
  • 最終,withSelfMethod() 屬性標記關係為自鏈接
  • 5. 關係

    在上一節中,我們展示了一個自引用關係。但是,更復雜的系統也可能涉及其他關係。

    例如,一個客户可以與訂單存在關係。 讓我們將訂單類建模為資源:

    public class Order extends RepresentationModel<Order> {
        private String orderId;
        private double price;
        private int quantity;
    
        // standard getters and setters
    }
    

    此時,我們可以擴展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;
    }
    

    我們的方法返回一個CollectionModel對象,以符合HAL返回類型,以及每個訂單的“_self”鏈接和完整的列表。

    這裏值得注意的是,客户訂單的超鏈接取決於getOrdersForCustomer()方法的映射。 我們將這些類型的鏈接稱為方法鏈接,並展示如何WebMvcLinkBuilder可以幫助創建它們。

    6. 鏈接到控制器方法

    WebMvcLinkBuilder 提供了豐富的支持,用於 Spring MVC 控制器。以下示例展示瞭如何基於 CustomerController 類中的 getOrdersForCustomer() 方法構建 HATEOAS 超鏈接:

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

    methodOn() 方法通過在代理控制器上對目標方法進行空調用來獲取方法映射,並將 customerId 設置為 URI 的路徑變量。

    7. Spring HATEOAS in Action

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

    @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:

    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.

    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:

    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. 結論

    在本教程中,我們討論瞭如何使用 Spring HATEOAS 項目構建基於超媒體的 Spring REST Web 服務。

    在示例中,我們可以看到客户端可以有一個單一的入口點,並且可以根據響應表示中的元數據採取進一步的操作。

    這允許服務器在不破壞客户端的情況下更改其 URI 方案。 此外,應用程序可以通過在表示中添加新的鏈接或 URI 來宣傳新的功能。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.