6. 客户端支持

本节介绍 Spring HATEOAS 对客户端的支持。spring-doc.cadn.net.cn

6.1. Traverson

Spring HATEOAS 提供了一个用于客户端服务遍历的 API。它的灵感来源于 Traverson JavaScript 库。 以下示例展示了如何使用它:spring-doc.cadn.net.cn

Map<String, Object> parameters = new HashMap<>();
parameters.put("user", 27);

Traverson traverson = new Traverson(URI.create("http://localhost:8080/api/"), MediaTypes.HAL_JSON);
String name = traverson
    .follow("movies", "movie", "actor").withTemplateParameters(parameters)
    .toObject("$.name");

您可以通过将其指向一个 REST 服务器并配置您希望设置为 Accept 头的媒体类型,来设置一个 Traverson 实例。然后,您可以定义想要发现和跟随的关系名称。关系名称可以是简单名称,也可以是 JSONPath 表达式(以 $ 开头)。spring-doc.cadn.net.cn

随后,示例将一个参数映射传入 Traverson 实例。这些参数用于展开遍历过程中发现的(模板化)URI。遍历通过访问最终遍历的表示而结束。在前面的示例中,我们评估了一个 JSONPath 表达式以访问演员的姓名。spring-doc.cadn.net.cn

前面的示例是最简单的遍历版本,其中 rel 值是字符串,并且在每一跳都应用相同的模板参数。spring-doc.cadn.net.cn

在每个层级都有更多选项来自定义模板参数。 以下示例展示了这些选项。spring-doc.cadn.net.cn

ParameterizedTypeReference<EntityModel<Item>> resourceParameterizedTypeReference = new ParameterizedTypeReference<EntityModel<Item>>() {};

EntityModel<Item> itemResource = traverson.//
    follow(rel("items").withParameter("projection", "noImages")).//
    follow("$._embedded.items[0]._links.self.href").//
    toObject(resourceParameterizedTypeReference);

静态 rel(…​) 函数是一种定义单个 Hop 的便捷方式。使用 .withParameter(key, value) 可以简化 URI 模板变量的指定。spring-doc.cadn.net.cn

.withParameter() 返回一个新的可链式调用的 Hop 对象。您可以按需串联任意数量的 .withParameter,最终得到一个单一的 Hop 定义。 以下示例展示了一种实现方式:
ParameterizedTypeReference<EntityModel<Item>> resourceParameterizedTypeReference = new ParameterizedTypeReference<EntityModel<Item>>() {};

Map<String, Object> params = Collections.singletonMap("projection", "noImages");

EntityModel<Item> itemResource = traverson.//
    follow(rel("items").withParameters(params)).//
    follow("$._embedded.items[0]._links.self.href").//
    toObject(resourceParameterizedTypeReference);

您也可以使用 .withParameters(Map) 加载整个参数 Mapspring-doc.cadn.net.cn

follow() 是可链式调用的,这意味着您可以将多个跳转串联起来,如前面的示例所示。您可以放置多个基于字符串的 rel 值(follow("items", "item")),或者使用特定参数进行单次跳转。

6.1.1. EntityModel<T> vs. CollectionModel<T>

迄今为止展示的示例演示了如何绕过 Java 的类型擦除,并将单个 JSON 格式的资源转换为 EntityModel<Item> 对象。但是,如果您获取的是一个集合(例如 \_embedded HAL 集合),该怎么办? 只需稍作调整即可实现,如下例所示:spring-doc.cadn.net.cn

CollectionModelType<Item> collectionModelType =
    new TypeReferences.CollectionModelType<Item>() {};

CollectionModel<Item> itemResource = traverson.//
    follow(rel("items")).//
    toObject(collectionModelType);

此方法不是获取单个资源,而是将集合反序列化为 CollectionModelspring-doc.cadn.net.cn

在使用支持超媒体的表示时,一个常见任务是在其中查找具有特定关系类型的链接。Spring HATEOAS 为默认表示渲染或 HAL 提供了基于 JSONPathLinkDiscoverer 接口实现。当使用 @EnableHypermediaSupport 时,我们会自动将一个支持所配置超媒体类型的实例作为 Spring Bean 暴露出来。spring-doc.cadn.net.cn

或者,您可以按以下方式设置和使用实例:spring-doc.cadn.net.cn

String content = "{'_links' :  { 'foo' : { 'href' : '/foo/bar' }}}";
LinkDiscoverer discoverer = new HalLinkDiscoverer();
Link link = discoverer.findLinkWithRel("foo", content);

assertThat(link.getRel(), is("foo"));
assertThat(link.getHref(), is("/foo/bar"));

6.3. 配置 WebClient 实例

如果您需要配置一个 WebClient 来支持超媒体,这很简单。请按照以下方式获取 HypermediaWebClientConfigurerspring-doc.cadn.net.cn

示例 43. 自行配置 WebClient
@Bean
WebClient.Builder hypermediaWebClient(HypermediaWebClientConfigurer configurer) { (1)
 return configurer.registerHypermediaTypes(WebClient.builder()); (2)
}
1 在您的 @Configuration 类中,获取 Spring HATEOAS 注册的 HypermediaWebClientConfigurer Bean 的副本。
2 创建 WebClient.Builder 后,使用配置器注册超媒体类型。
HypermediaWebClientConfigurer会使用WebClient.Builder注册所有正确的编码器和解码器。要使用它, 您需要将构建器注入到应用程序中的某个位置,并运行build()方法来生成一个WebClient

如果您正在使用 Spring Boot,还有另一种方法:WebClientCustomizerspring-doc.cadn.net.cn

示例 44. 让 Spring Boot 配置各项内容
@Bean (4)
WebClientCustomizer hypermediaWebClientCustomizer(HypermediaWebClientConfigurer configurer) { (1)
    return webClientBuilder -> { (2)
        configurer.registerHypermediaTypes(webClientBuilder); (3)
    };
}
1 创建 Spring Bean 时,请请求 Spring HATEOAS 的 HypermediaWebClientConfigurer Bean 的副本。
2 使用 Java 8 的 lambda 表达式来定义一个 WebClientCustomizer
3 在函数调用内部,应用 registerHypermediaTypes 方法。
4 将其整体作为一个 Spring bean 返回,以便 Spring Boot 能够识别并将其应用到其自动配置的 WebClient.Builder bean 上。

在此阶段,每当您需要一个具体的 WebClient 时,只需将 WebClient.Builder 注入到您的代码中,并使用 build()WebClient 实例 将能够通过超媒体进行交互。spring-doc.cadn.net.cn

6.4. 配置WebTestClient实例

在使用支持超媒体的表示时,一个常见的任务是使用WebTestClient运行各种测试。spring-doc.cadn.net.cn

要在测试用例中配置 WebTestClient 的实例,请参考此示例:spring-doc.cadn.net.cn

示例 45. 使用 Spring HATEOAS 时配置 WebTestClient
@Test // #1225
void webTestClientShouldSupportHypermediaDeserialization() {

  // Configure an application context programmatically.
  AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
  context.register(HalConfig.class); (1)
  context.refresh();

  // Create an instance of a controller for testing
  WebFluxEmployeeController controller = context.getBean(WebFluxEmployeeController.class);
  controller.reset();

  // Extract the WebTestClientConfigurer from the app context.
  HypermediaWebTestClientConfigurer configurer = context.getBean(HypermediaWebTestClientConfigurer.class);

  // Create a WebTestClient by binding to the controller and applying the hypermedia configurer.
  WebTestClient client = WebTestClient.bindToApplicationContext(context).build().mutateWith(configurer); (2)

  // Exercise the controller.
  client.get().uri("http://localhost/employees").accept(HAL_JSON) //
      .exchange() //
      .expectStatus().isOk() //
      .expectBody(new TypeReferences.CollectionModelType<EntityModel<Employee>>() {}) (3)
      .consumeWith(result -> {
        CollectionModel<EntityModel<Employee>> model = result.getResponseBody(); (4)

        // Assert against the hypermedia model.
        assertThat(model.getRequiredLink(IanaLinkRelations.SELF)).isEqualTo(Link.of("http://localhost/employees"));
        assertThat(model.getContent()).hasSize(2);
      });
}
1 注册您的配置类,该类使用 @EnableHypermediaSupport 来启用 HAL 支持。
2 使用 HypermediaWebTestClientConfigurer 来应用超媒体支持。
3 使用 Spring HATEOAS 的 TypeReferences.CollectionModelType 助手请求返回 CollectionModel<EntityModel<Employee>>
4 在获取到 Spring HATEOAS 格式的"body"后,对其进行断言验证!
WebTestClient 是一个不可变的值类型,因此您无法就地修改它。HypermediaWebClientConfigurer 返回一个变更后的变体,您必须捕获该变体才能使用它。

如果您正在使用 Spring Boot,还有其他选项,例如:spring-doc.cadn.net.cn

示例 46. 使用 Spring Boot 时配置WebTestClient
@SpringBootTest
@AutoConfigureWebTestClient (1)
class WebClientBasedTests {

    @Test
    void exampleTest(@Autowired WebTestClient.Builder builder, @Autowired HypermediaWebTestClientConfigurer configurer) { (2)
        client = builder.apply(configurer).build(); (3)

        client.get().uri("/") //
                .exchange() //
                .expectBody(new TypeReferences.EntityModelType<Employee>() {}) (4)
                .consumeWith(result -> {
                    // assert against this EntityModel<Employee>!
                });
    }
}
1 这是 Spring Boot 的测试注解,将为该测试类配置一个 WebTestClient.Builder
2 将 Spring Boot 的 WebTestClient.Builder 和 Spring HATEOAS 的配置器作为方法参数自动装配到 builder 中。
3 使用 HypermediaWebTestClientConfigurer 注册对超媒体的支持。
4 使用 TypeReferences 表示您希望返回 EntityModel<Employee>

同样,您可以使用与前面示例类似的断言。spring-doc.cadn.net.cn

构建测试用例还有许多其他方法。WebTestClient 可以绑定到控制器、函数和 URL。本节并不旨在展示所有内容,而是提供一些入门示例。关键在于,通过应用 HypermediaWebTestClientConfigurer,任何 WebTestClient 实例都可以被修改以支持超媒体。spring-doc.cadn.net.cn

6.5. 配置 RestTemplate 实例

如果您想创建自己的RestTemplate副本并配置为支持超媒体,您可以使用HypermediaRestTemplateConfigurerspring-doc.cadn.net.cn

示例 47. 自行配置 RestTemplate
/**
 * Use the {@link HypermediaRestTemplateConfigurer} to configure a {@link RestTemplate}.
 */
@Bean
RestTemplate hypermediaRestTemplate(HypermediaRestTemplateConfigurer configurer) { (1)
	return configurer.registerHypermediaTypes(new RestTemplate()); (2)
}
1 在您的 @Configuration 类中,获取 Spring HATEOAS 注册的 HypermediaRestTemplateConfigurer Bean 的副本。
2 创建 RestTemplate 后,使用配置器应用超媒体类型。

您可以自由地将此模式应用于任何您需要RestTemplate的实例,无论是用于创建已注册的 Bean,还是在您定义的服务内部使用。spring-doc.cadn.net.cn

如果您正在使用 Spring Boot,还有另一种方法。spring-doc.cadn.net.cn

通常,Spring Boot 已经不再采用在应用上下文中注册 RestTemplate bean 的概念。spring-doc.cadn.net.cn

为了弥补这一点,Spring Boot 提供了一个RestTemplateBuilder。这个自动配置的 Bean 允许您定义用于构建RestTemplate实例的各种 Bean。您可以请求一个RestTemplateBuilder Bean,调用其build()方法,然后应用最终设置(例如凭据和其他详细信息)。spring-doc.cadn.net.cn

要注册基于超媒体的消息转换器,请将以下内容添加到您的代码中:spring-doc.cadn.net.cn

示例 48. 让 Spring Boot 配置各项内容
@Bean (4)
RestTemplateCustomizer hypermediaRestTemplateCustomizer(HypermediaRestTemplateConfigurer configurer) { (1)
    return restTemplate -> { (2)
        configurer.registerHypermediaTypes(restTemplate); (3)
    };
}
1 创建 Spring Bean 时,请请求 Spring HATEOAS 的 HypermediaRestTemplateConfigurer Bean 的副本。
2 使用 Java 8 的 lambda 表达式来定义一个 RestTemplateCustomizer
3 在函数调用内部,应用 registerHypermediaTypes 方法。
4 将整个内容作为一个 Spring bean 返回,以便 Spring Boot 能够识别它并将其应用到其自动配置的 RestTemplateBuilder 中。

在此阶段,每当您需要一个具体的 RestTemplate 时,只需将 RestTemplateBuilder 注入到您的代码中,并使用 build()RestTemplate 实例 将能够通过超媒体进行交互。spring-doc.cadn.net.cn