Spring Boot RESTFul 实践之HATEOAS

x33g5p2x  于2022-09-18 转载在 Spring  
字(4.8k)|赞(0)|评价(0)|浏览(462)

在这篇文章中,我们将学习如何在Spring boot的restful web应用中实现hateoas。

HATEOAS简介

超媒体是RESTful服务中最重要的方面。简而言之,超媒体是一种格式,其中一个资源的响应包含指向其他相关资源的链接。任何使用这种格式的系统都被称为超文本作为应用状态的引擎,或者简称为**HATEOAS**。

把一个网页看作是一种资源。它包含指向其他网页的链接。这就是我们所说的超文本页面(HTML)。现在想象一下,如果可以对JSON响应做同样的事情。这就是media type: application/hal+xmlmedia type: application/hal+json的作用。

有了这些新的超媒体格式,一个简单的订单对象将看起来像下面这样。

{
  "orderId": 1,
  "total": 54.99,
  "_links": {
    "self": {
      "href": "http://localhost:8080/orders/1"
    },
    "account": {
      "href": "http://localhost:8080/accounts/2"
    },
    "items": {
      "href": "http://localhost:8080/orders/1/items"
    }
  }
}
Code language: JSON / JSON with Comments (json)

响应可能看起来很简单。收到这些响应的客户只需要按照链接的资源来获得他们想要的信息。但是对于一个服务器来说,要生成这些引用,它需要有适当的POJO,可以接受多个链接。一种在当前请求的上下文中引用其他控制器方法的方法等。

如果我们必须自己实现所有这些,我们最终会有一大堆代码逻辑。多亏了spring boot中的HATEOAS启动器,这项工作对开发者来说变得简单了。

让我们看看如何将上一篇文章中的例子转换为符合HATEOAS的RESTful服务。

HATEOAS的Spring Boot项目设置

这个设置真的很简单。在上一篇文章的源代码中,只需将spring-boot-starter-web替换为spring-boot-starter-hateoas。这一步非常简单,因为这个启动器内部只包含web启动器和spring-hateoas库。这个库带有帮助方法和超文本应用语言(HAL)的模型

现在maven部分已经完成,我们需要对我们的代码做一些修改。让我们回顾一下我们的项目控制器。如果不做任何改动,控制器的方法看起来如下。

@GetMapping("/")
    List<Item> get() {
        return itemRepository.findAll();
    }
    
    @GetMapping("/{itemId}")
    Item get(@PathVariable Integer itemId) {
        return itemRepository.findById(itemId).orElseThrow(RuntimeException::new);
    }
Code language: CSS (css)

而这些控制器方法的响应是这样的

GET http://localhost:8080/items
[
  {
    "id": 1,
    "itemName": "Shoe",
    "price": 12.99
  },
  {
    "id": 2,
    "itemName": "Shirt",
    "price": 8.99
  }
]
Code language: JavaScript (javascript)
GET http://localhost:8080/items/2
{
  "id": 2,
  "itemName": "Shirt",
  "price": 8.99
}
Code language: JavaScript (javascript)

为了给这些响应添加_linksspring-hateoas提供了两个主要的封装类。它们是EntityModelCollectionModel。使用这些类,我们可以用几行代码添加相关链接。让我们看看我是如何做到的。

package com.springhow.examples.springboot.rest.controller;

import com.springhow.examples.springboot.rest.entities.Item;
import com.springhow.examples.springboot.rest.entities.repositories.ItemRepository;
import org.springframework.hateoas.CollectionModel;
import org.springframework.hateoas.EntityModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo;
import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn;


@RestController
@RequestMapping("/items")
public class ItemController {

    private final ItemRepository itemRepository;

    public ItemController(ItemRepository itemRepository) {
        this.itemRepository = itemRepository;
    }

    @GetMapping("/")
    CollectionModel<EntityModel<Item>> get() {
        List<EntityModel<Item>> items = itemRepository.findAll().stream().map(item -> EntityModel.of(item,
                linkTo(methodOn(ItemController.class).get(item.getId())).withSelfRel(),
                linkTo(methodOn(ItemController.class).get()).withRel("items")))
                .collect(Collectors.toList());
        return CollectionModel.of(items, linkTo(methodOn(ItemController.class).get()).withSelfRel());
    }

    @GetMapping("/{itemId}")
    EntityModel<Item> get(@PathVariable Integer itemId) {
        Item item = itemRepository.findById(itemId).orElseThrow(RuntimeException::new);
        return EntityModel.of(item,
                linkTo(methodOn(ItemController.class).get(itemId)).withSelfRel(),
                linkTo(methodOn(ItemController.class).get()).withRel("items"));
    }
}
Code language: JavaScript (javascript)

这里的linkTomethodOn是来自hateoas库的静态方法,帮助处理生成URL。完成上述设置后,让我们来启动API。

GET http://localhost:8080/items/1

{
  "id" : 1,
  "itemName" : "Shoe",
  "price" : 12.99,
  "_links" : {
    "self" : {
      "href" : "http://localhost:8080/items/1"
    },
    "items" : {
      "href" : "http://localhost:8080/items/"
    }
  }
}
Code language: JavaScript (javascript)

再举一个例子,一旦提交了购物车,我们就希望能创建一个订单。在这种情况下,购物车与已创建的订单没有直接关系。然而,我们可以通过HAL提供一个链接到创建的订单资源,就像这样。

@PutMapping("/{cartId}")
    EntityModel<Cart> update(@PathVariable Integer cartId) {
        Cart cartFromDB = cartRepository.findById(cartId).orElseThrow(RuntimeException::new);
        cartFromDB.setStatus(CartStatus.SUBMITTED);
        //Ignoring validation if the cart content has changed for simplicity.
        OrderHeader orderHeader = orderService.saveOrder(cartFromDB);
        return EntityModel.of(cartFromDB,
                linkTo(methodOn(CartController.class).get(cartFromDB.getId())).withSelfRel(),
                linkTo(methodOn(OrderController.class).getOrder(orderHeader.getId())).withRel("order"));    
}
Code language: JavaScript (javascript)

注意,突出显示的linkTo方法是指一个不同的控制器。以前,这是不可能的。spring boot starter for hateoas的hateoas包有助于解决这些问题。

另外,请注意,底层的服务方法完全没有变化。就RESTfulness而言,POSTs仍然可以接受一个简单的POJO作为输入。

相关文章

微信公众号

最新文章

更多