2. 基础概念
本节介绍 Spring HATEOAS 的基础知识及其核心领域抽象。
2.1. 链接
超媒体的基本思想是用超媒体元素丰富资源的表示形式。 其中最简单的形式是链接。 它们向客户端表明可以导航到某个特定资源。 相关资源的语义在所谓的“链接关系”中定义。 您可能已经在 HTML 文件的头部见过这种用法:
<link href="theme.css" rel="stylesheet" type="text/css" />
如您所见,该链接指向资源 theme.css,并表明它是一个样式表。
链接通常携带额外信息,例如所指向资源将返回的媒体类型。
然而,链接的基本构建块是其引用和关系。
Spring HATEOAS 让您可以通过其不可变的 Link 值类型来操作链接。
其构造函数同时接受超文本引用和链接关系,后者默认为 IANA 链接关系 self。
有关后者的更多信息,请参阅 链接关系。
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 实例上调用相应的 wither 方法来设置它们。
在Spring MVC 中构建链接和Spring WebFlux 中构建链接中查找有关如何创建指向 Spring MVC 和 Spring WebFlux 控制器的链接的更多信息。
2.2. URI 模板
对于 Spring HATEOAS Link,超文本引用不仅可以是 URI,还可以是根据 RFC-6570 定义的 URI 模板。
URI 模板包含所谓的模板变量,并允许对这些参数进行展开。
这使得客户端能够将参数化模板转换为 URI,而无需了解最终 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 | Link 实例表示已模板化,即它包含一个 URI 模板。 |
| 2 | 它暴露了模板中包含的参数。 |
| 3 | 它允许扩展参数。 |
URI 模板可以手动构建,并在稍后添加模板变量。
UriTemplate template = UriTemplate.of("/{segment}/something")
.with(new TemplateVariable("parameter", VariableType.REQUEST_PARAM);
assertThat(template.toString()).isEqualTo("/{segment}/something{?parameter}");
2.3. 链接关系
为了表示目标资源与当前资源之间的关系,使用了所谓的链接关系。
Spring HATEOAS 提供了一个 LinkRelation 类型,以便轻松创建基于 String 的实例。
2.3.1. IANA 链接关系
互联网号码分配机构包含一组 预定义的链接关系。
它们可以通过 IanaLinkRelations 来引用。
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 的类。
它本质上是一个用于容纳 Link 集合的容器,并提供了便捷的方法将这些内容添加到模型中。
随后,这些模型可以被渲染为各种媒体类型格式,从而定义超媒体元素在表示中的外观。
有关更多信息,请参阅 媒体类型。
RepresentationModel 类的层次结构使用 RepresentationModel 的默认方式是创建其子类,以包含表示形式应具备的所有属性,创建该类的实例,填充属性并为其添加链接。
class PersonModel extends RepresentationModel<PersonModel> {
String firstname, lastname;
}
泛型自类型化对于让 RepresentationModel.add(…) 返回其自身实例是必要的。
现在可以像这样使用模型类型:
PersonModel model = new PersonModel();
model.firstname = "Dave";
model.lastname = "Matthews";
model.add(Link.of("https://myhost/people/42"));
如果您从 Spring MVC 或 WebFlux 控制器返回了这样一个实例,并且客户端发送的 Accept 头被设置为 application/hal+json,则响应将如下所示:
{
"_links" : {
"self" : {
"href" : "https://myhost/people/42"
}
},
"firstname" : "Dave",
"lastname" : "Matthews"
}
2.4.1. 项资源表示模型
对于由单一对象或概念支持的资源,存在一个便捷的 EntityModel 类型。
您无需为每个概念创建自定义模型类型,只需重用现有类型并将其实例包装到 EntityModel 中即可。
EntityModel 包装现有对象Person person = new Person("Dave", "Matthews");
EntityModel<Person> model = EntityModel.of(person);
2.4.2. 集合资源表示模型
对于概念上是集合的资源,可以使用 CollectionModel。
其元素可以是简单对象,也可以是 RepresentationModel 实例。
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) 将其添加到空实例中;二是作为后备方案,以防底层集合可能为空:
Iterable<Person> people = repository.findAll();
var model = CollectionModel.of(people).withFallbackType(Person.class);