> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v5/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v5/di-6-zhang-chuang-jian-rest-fu-wu/6.2-qi-yong-chao-mei-ti/6.2.1-tian-jia-chao-lian-jie.md).

# 6.2.1 添加超链接

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

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

{% code title="程序清单 6.4 为资源添加超链接" %}

```java
@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;
}
```

{% endcode %}

在这个新版本的 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，如下所示：

```java
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() 方法：

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

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