2. 基本面

本节介绍 Spring HATEOAS 的基础知识及其基本领域抽象。spring-doc.cadn.net.cn

超媒体的基本思想是用超媒体元素丰富资源的表示。 最简单的形式是链接。 它们指示客户端可以导航到某个资源。 相关资源的语义在所谓的链接关系中定义。 您可能已经在 HTML 文件的标题中看到了这一点:spring-doc.cadn.net.cn

示例 2.HTML 文档中的链接
<link href="theme.css" rel="stylesheet" type="text/css" />

如您所见,链接指向资源theme.css并表示它是样式表。 链接通常带有附加信息,例如资源指向的媒体类型将返回。 然而,链接的基本构建块是它的引用和关系。spring-doc.cadn.net.cn

Spring HATEOAS 允许您通过其不可变性处理链接Link值类型。 它的构造函数同时采用超文本引用和链接关系,后者默认为 IANA 链接关系self. 在链接关系中阅读有关后者的更多信息。spring-doc.cadn.net.cn

示例 3.使用链接
Link link = Link.of("/something");
assertThat(link.getHref()).isEqualTo("/something");
assertThat(link.getRel()).isEqualTo(IanaLinkRelations.SELF);

link = Link.of("/something", "my-rel");
assertThat(link.getHref()).isEqualTo("/something");
assertThat(link.getRel()).isEqualTo(LinkRelation.of("my-rel"));

Link公开 RFC-8288 中定义的其他属性。 你可以通过在Link实例。spring-doc.cadn.net.cn

有关如何创建指向 Spring MVC 和 Spring WebFlux 控制器的链接的更多信息,请参阅在 Spring MVC 中构建链接在 Spring WebFlux 中构建链接spring-doc.cadn.net.cn

2.2. URI 模板

Spring HATEOASLink,超文本引用不仅可以是 URI,还可以是根据 RFC-6570 的 URI 模板。 URI 模板包含所谓的模板变量,并允许扩展这些参数。 这允许客户端将参数化模板转换为 URI,而无需了解最终 URI 的结构,它只需要知道变量的名称。spring-doc.cadn.net.cn

示例 4.使用带有模板化 URI 的链接
Link link = Link.of("/{segment}/something{?parameter}");
assertThat(link.isTemplated()).isTrue(); (1)
assertThat(link.getVariableNames()).contains("segment", "parameter"); (2)

Map<String, Object> values = new HashMap<>();
values.put("segment", "path");
values.put("parameter", 42);

assertThat(link.expand(values).getHref()) (3)
    .isEqualTo("/path/something?parameter=42");
1 Linkinstance 表示已模板化,即它包含 URI 模板。
2 它公开模板中包含的参数。
3 它允许扩展参数。

可以手动构建 URI 模板,稍后添加模板变量。spring-doc.cadn.net.cn

示例 5.使用 URI 模板
UriTemplate template = UriTemplate.of("/{segment}/something")
  .with(new TemplateVariable("parameter", VariableType.REQUEST_PARAM);

assertThat(template.toString()).isEqualTo("/{segment}/something{?parameter}");

为了指示目标资源与当前资源的关系,使用了所谓的链接关系。 Spring HATEOAS 提供了一个LinkRelation类型以轻松创建String-基于它的实例。spring-doc.cadn.net.cn

Internet 号码分配机构包含一组预定义的链路关系。 可以通过以下方式引用它们IanaLinkRelations.spring-doc.cadn.net.cn

示例 6.使用 IANA 链接关系
Link link = Link.of("/some-resource"), IanaLinkRelations.NEXT);

assertThat(link.getRel()).isEqualTo(LinkRelation.of("next"));
assertThat(IanaLinkRelation.isIanaRel(link.getRel())).isTrue();

2.4. 表示模型

为了轻松创建超媒体丰富的表示,Spring HATEOAS 提供了一组类RepresentationModel在他们的根源上。 它基本上是一个容器,用于集合Links 并具有将它们添加到模型中的便捷方法。 这些模型稍后可以呈现为各种媒体类型格式,这些格式将定义超媒体元素在表示中的外观。 有关这方面的更多信息,请查看媒体类型spring-doc.cadn.net.cn

示例 7.这RepresentationModel类层次结构
图表类

使用RepresentationModel是创建它的子类以包含表示应该包含的所有属性,创建该类的实例,填充属性并使用链接丰富它。spring-doc.cadn.net.cn

示例 8.示例表示模型类型
class PersonModel extends RepresentationModel<PersonModel> {

  String firstname, lastname;
}

通用自键型是必要的,让RepresentationModel.add(…)返回自身的实例。 模型类型现在可以这样使用:spring-doc.cadn.net.cn

示例 9.使用人员表示模型
PersonModel model = new PersonModel();
model.firstname = "Dave";
model.lastname = "Matthews";
model.add(Link.of("https://myhost/people/42"));

如果您从 Spring MVC 或 WebFlux 控制器返回此类实例,并且客户端发送了Acceptheader 设置为application/hal+json,响应如下所示:spring-doc.cadn.net.cn

示例 10.为人员表示模型生成的 HAL 表示形式
{
  "_links" : {
    "self" : {
      "href" : "https://myhost/people/42"
    }
  },
  "firstname" : "Dave",
  "lastname" : "Matthews"
}

2.4.1. 项目资源表示模型

对于由单一对象或概念支持的资源,方便EntityModeltype 存在。无需为每个概念创建自定义模型类型,只需重用已经存在的类型并将其实例包装到EntityModel.spring-doc.cadn.net.cn

示例 11. 用EntityModel包装现有对象
Person person = new Person("Dave", "Matthews");
EntityModel<Person> model = EntityModel.of(person);

2.4.2. 集合资源表示模型

对于概念上属于集合的资源,一个CollectionModel可用。它的元素可以是简单对象,也可以是RepresentationModel实例。spring-doc.cadn.net.cn

示例 12. 用CollectionModel包装现有对象的集合
Collection<Person> people = Collections.singleton(new Person("Dave", "Matthews"));
CollectionModel<Person> model = CollectionModel.of(people);

虽然EntityModel被限制为始终包含有效负载,因此允许推理唯一实例上的类型排列,一个CollectionModel的底层集合可以是空的。由于 Java 的类型擦除,我们实际上无法检测到CollectionModel<Person> model = CollectionModel.empty()实际上是一个CollectionModel<Person>因为我们看到的只是运行时实例和一个空集合。可以通过以下方式将缺失的类型信息添加到模型中,方法是在构造时将其添加到空实例中CollectionModel.empty(Person.class)或者作为底层集合可能为空的回退:spring-doc.cadn.net.cn

Iterable<Person> people = repository.findAll();
var model = CollectionModel.of(people).withFallbackType(Person.class);