6. Client-side Support

This section describes Spring HATEOAS’s support for clients.spring-doc.cn

6.1. Traverson

Spring HATEOAS provides an API for client-side service traversal. It is inspired by the Traverson JavaScript library. The following example shows how to use it:spring-doc.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");

You can set up a Traverson instance by pointing it to a REST server and configuring the media types you want to set as Accept headers. You can then define the relation names you want to discover and follow. Relation names can either be simple names or JSONPath expressions (starting with an $).spring-doc.cn

The sample then hands a parameter map into the Traverson instance. The parameters are used to expand URIs (which are templated) found during the traversal. The traversal is concluded by accessing the representation of the final traversal. In the preceding example, we evaluate a JSONPath expression to access the actor’s name.spring-doc.cn

The preceding example is the simplest version of traversal, where the rel values are strings and, at each hop, the same template parameters are applied.spring-doc.cn

There are more options to customize template parameters at each level. The following example shows these options.spring-doc.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);

The static rel(…​) function is a convenient way to define a single Hop. Using .withParameter(key, value) makes it simple to specify URI template variables.spring-doc.cn

.withParameter() returns a new Hop object that is chainable. You can string together as many .withParameter as you like. The result is a single Hop definition. The following example shows one way to do so:
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);

You can also load an entire Map of parameters by using .withParameters(Map).spring-doc.cn

follow() is chainable, meaning you can string together multiple hops, as shown in the preceding examples. You can either put multiple string-based rel values (follow("items", "item")) or a single hop with specific parameters.

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

The examples shown so far demonstrate how to sidestep Java’s type erasure and convert a single JSON-formatted resource into a EntityModel<Item> object. However, what if you get a collection like an \_embedded HAL collection? You can do so with only one slight tweak, as the following example shows:spring-doc.cn

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

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

Instead of fetching a single resource, this one deserializes a collection into CollectionModel.spring-doc.cn

When working with hypermedia enabled representations, a common task is to find a link with a particular relation type in it. Spring HATEOAS provides JSONPath-based implementations of the LinkDiscoverer interface for either the default representation rendering or HAL out of the box. When using @EnableHypermediaSupport, we automatically expose an instance supporting the configured hypermedia type as a Spring bean.spring-doc.cn

Alternatively, you can set up and use an instance as follows:spring-doc.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. Configuring WebClient instances

If you need configure a WebClient to speak hypermedia, it’s easy. Get a hold of the HypermediaWebClientConfigurer as shown below:spring-doc.cn

Example 43. Configuring a WebClient yourself
@Bean
WebClient.Builder hypermediaWebClient(HypermediaWebClientConfigurer configurer) { (1)
 return configurer.registerHypermediaTypes(WebClient.builder()); (2)
}
1 Inside your @Configuration class, get a copy of the HypermediaWebClientConfigurer bean Spring HATEOAS registers.
2 After creating a WebClient.Builder, use the configurer to register hypermedia types.
What HypermediaWebClientConfigurer does it register all the right encoders and decoders with a WebClient.Builder. To make use of it, you need to inject the builder somewhere into your application, and run the build() method to produce a WebClient.

If you’re using Spring Boot, there is another way: the WebClientCustomizer.spring-doc.cn

Example 44. Letting Spring Boot configure things
@Bean (4)
WebClientCustomizer hypermediaWebClientCustomizer(HypermediaWebClientConfigurer configurer) { (1)
    return webClientBuilder -> { (2)
        configurer.registerHypermediaTypes(webClientBuilder); (3)
    };
}
1 When creating a Spring bean, request a copy of Spring HATEOAS’s HypermediaWebClientConfigurer bean.
2 Use a Java 8 lambda expression to define a WebClientCustomizer.
3 Inside the function call, apply the registerHypermediaTypes method.
4 Return the whole thing as a Spring bean so Spring Boot can pick it up and apply it to its autoconfigured WebClient.Builder bean.

At this stage, whenever you need a concrete WebClient, simply inject WebClient.Builder into your code, and use build(). The WebClient instance will be able to interact using hypermedia.spring-doc.cn

6.4. Configuring WebTestClient Instances

When working with hypermedia-enabled representations, a common task is to run various tests by using WebTestClient.spring-doc.cn

To configure an instance of WebTestClient in a test case, check out this example:spring-doc.cn

Example 45. Configuring WebTestClient when using Spring HATEOAS
@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 Register your configuration class that uses @EnableHypermediaSupport to enable HAL support.
2 Use HypermediaWebTestClientConfigurer to apply hypermedia support.
3 Ask for a response of CollectionModel<EntityModel<Employee>> using Spring HATEOAS’s TypeReferences.CollectionModelType helper.
4 After getting the "body" in Spring HATEOAS format, assert against it!
WebTestClient is an immutable value type, so you can’t alter it in place. HypermediaWebClientConfigurer returns a mutated variant that you must then capture to use it.

If you are using Spring Boot, there are additional options, like this:spring-doc.cn

Example 46. Configuring WebTestClient when using Spring Boot
@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 This is Spring Boot’s test annotation that will configure a WebTestClient.Builder for this test class.
2 Autowire Spring Boot’s WebTestClient.Builder into builder and Spring HATEOAS’s configurer as method parameters.
3 Use HypermediaWebTestClientConfigurer to register support for hypermedia.
4 Signal you want an EntityModel<Employee> returned using TypeReferences.

Again, you can use similar assertions as the earlier example.spring-doc.cn

There are many other ways to fashion test cases. WebTestClient can be bound to controllers, functions, and URLs. This section isn’t meant to show all that. Instead, this gives you some examples to get started. The important thing is that by applying HypermediaWebTestClientConfigurer, any instance of WebTestClient can be altered to handle hypermedia.spring-doc.cn

6.5. Configuring RestTemplate instances

If you want to create your own copy of RestTemplate, configured to speak hypermedia, you can use the HypermediaRestTemplateConfigurer:spring-doc.cn

Example 47. Configuring RestTemplate yourself
/**
 * Use the {@link HypermediaRestTemplateConfigurer} to configure a {@link RestTemplate}.
 */
@Bean
RestTemplate hypermediaRestTemplate(HypermediaRestTemplateConfigurer configurer) { (1)
	return configurer.registerHypermediaTypes(new RestTemplate()); (2)
}
1 Inside your @Configuration class, get a copy of the HypermediaRestTemplateConfigurer bean Spring HATEOAS registers.
2 After creating a RestTemplate, use the configurer to apply hypermedia types.

You are free to apply this pattern to any instance of RestTemplate that you need, whether is to create a registered bean, or inside a service you define.spring-doc.cn

If you’re using Spring Boot, there is another approach.spring-doc.cn

In general, Spring Boot has moved away from the concept of registering a RestTemplate bean in the application context.spring-doc.cn

  • When talking to different services, you often need different credentials.spring-doc.cn

  • When RestTemplate uses an underlying connection pool, you run into additional issues.spring-doc.cn

  • Users often need different instances rather than a single bean.spring-doc.cn

To compensate for this, Spring Boot provides a RestTemplateBuilder. This autoconfigured bean lets you define various beans used to fashion a RestTemplate instance. You ask for a RestTemplateBuilder bean, call its build() method, and then apply final settings (such as credentials and other details).spring-doc.cn

To register hypermedia-based message converters, add the following to your code:spring-doc.cn

Example 48. Letting Spring Boot configure things
@Bean (4)
RestTemplateCustomizer hypermediaRestTemplateCustomizer(HypermediaRestTemplateConfigurer configurer) { (1)
    return restTemplate -> { (2)
        configurer.registerHypermediaTypes(restTemplate); (3)
    };
}
1 When creating a Spring bean, request a copy of Spring HATEOAS’s HypermediaRestTemplateConfigurer bean.
2 Use a Java 8 lambda expression to define a RestTemplateCustomizer.
3 Inside the function call, apply the registerHypermediaTypes method.
4 Return the whole thing as a Spring bean so Spring Boot can pick it up and apply it to its autoconfigured RestTemplateBuilder.

At this stage, whenever you need a concrete RestTemplate, simply inject RestTemplateBuilder into your code, and use build(). The RestTemplate instance will be able to interact using hypermedia.spring-doc.cn