6.2.1 添加超链接

Spring HATEOAS 提供了两种表示超链接资源的主要类型:Resource 和 Resources。Resource 类型表示单个资源,而 Resources 是资源的集合。这两种类型都能够携带其他资源的链接。当从 Spring MVC REST 控制器方法返回时,它们携带的链接将包含在客户端接收到的 JSON(或 XML)中。

要将超链接添加到最近创建的 tacos 列表,需要重新访问程序清单 6.2 中显示的 recentTacos() 方法。最初的实现返回了一个 List<Taco> 列表,这在当时是可以的,但是现在需要它来返回一个 Resources 对象。下面的程序清单显示了 recentTacos() 的新实现,其中包括在最近的 tacos 列表中启用超链接的第一步。

程序清单 6.4 为资源添加超链接
@GetMapping("/recent")
public Resources<Resource<Taco>> recentTacos() {
    PageRequest page = PageRequest.of(
        0, 12, Sort.by("createdAt").descending());
    
    List<Taco> tacos = tacoRepo.findAll(page).getContent();
    Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
    
    recentResources.add(
        new Link("http://localhost:8080/design/recent", "recents"));
    
    return recentResources;
}

在这个新版本的 recentTacos() 中,不再直接返回 tacos 列表。而是使用 Resources.wrap() 将 tacos 列表包装为 Resources<Resource<Taco>> 的实例,该实例最终从该方法返回。但是在返回 Resources 对象之前需要添加一个链接,该链接的关系名称为 recents,URL 为 http://localhost:8080/design/recent。因此,以下 JSON 片段包含在 API 请求返回的资源中:

"_links": {
    "recents": {
        "href": "http://localhost:8080/design/recent"
    }
}

这是一个好的开始,但你仍有一些工作要做。此时,添加的惟一链接就是整个列表;没有链接添加到 taco 资源本身或每个 taco 的成分,很快就会加上的。但首先,需要处理为 recents 链接提供的硬编码 URL。

像这样硬编码一个 URL 是非常糟糕的主意。除非 Taco Cloud 仅限于在自己的开发机器上运行应用程序,否则需要一种方法来避免在 URL 中硬编码 localhost:8080。幸运的是,Spring HATEOAS 以链接构建器的形式提供了帮助。

Spring HATEOAS 链接生成器中最有用的是 ControllerLinkBuilder。这个链接生成器非常聪明,无需硬编码就可以知道主机名是什么。它还提供了一个方便的连贯的 API,帮助你构建相对于任何控制器的基本 URL 的链接。

使用 ControllerLinkBuilder,可以重写硬编码的链接在 recentTacos() 中创建的 Link,如下所示:

Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
recentResources.add(
    ControllerLinkBuilder.linkTo(DesignTacoController.class)
                         .slash("recent")
                         .withRel("recents"));

不仅不再需要硬编码主机名,还不必指定 /design 路径。相反,需要一个指向 DesignTacoController 的链接,它的基本路径是 /design。ControllerLinkBuilder 使用控制器的基本路径作为正在创建的链接对象的基础。

接下来是对任何 Spring 项目中我最喜欢的方法之一的调用:slash()。我喜欢这个方法因为它简洁地描述了它要做的事情。它确实在 URL 后面附加了一个斜杠 / 和给定的值,因此,URL 的路径是 /design/recent

最后,为链接指定一个关系名。在本例中,关系被命名为 recents。

尽管我非常喜欢 slash() 方法,ControllerLinkBuilder 有另一个方法可以帮助消除与链接 url 相关的硬编码。可以通过给予它在控制器上的方法来调用 linkTo(),而不是调用 slash(),并让 ControllerLinkBuilder 从控制器基础路径和方法映射路径中派生出基础 URL。下面的代码以这种方式使用了 linkTo() 方法:

Resources<Resource<Taco>> recentResources = Resources.wrap(tacos);
recentResources.add(
    linkTo(methodOn(DesignTacoController.class).recentTacos())
    .withRel("recents"));

这里我决定静态地引用 linkTo() 和 methodOn() 方法(都来自 ControllerLinkBuilder),以使代码更易于阅读。methodOn() 方法获取控制器类并允许调用 recentTacos() 方法,该方法被 ControllerLinkBuilder 拦截,不仅用于确定控制器的基本路径,还用于确定映射到 recentTacos() 的路径。现在,整个 URL 都是从控制器的映射中派生出来的,而且绝对没有硬编码的部分,非常好~

最后更新于