6. Client-side Support
This section describes Spring HATEOAS’s support for clients.
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:
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 $
).
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.
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.
There are more options to customize template parameters at each level. The following example shows these options.
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.
.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)
.
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:
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
.
6.2. Using LinkDiscoverer
Instances
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.
Alternatively, you can set up and use an instance as follows:
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:
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
.
@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.
6.4. Configuring WebTestClient
Instances
When working with hypermedia-enabled representations, a common task is to run various tests by using WebTestClient
.
To configure an instance of WebTestClient
in a test case, check out this example:
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:
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.
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.
6.5. Configuring RestTemplate instances
If you want to create your own copy of RestTemplate
, configured to speak hypermedia, you can use the HypermediaRestTemplateConfigurer
:
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.
If you’re using Spring Boot, there is another approach.
In general, Spring Boot has moved away from the concept of registering a RestTemplate
bean in the application context.
-
When talking to different services, you often need different credentials.
-
When
RestTemplate
uses an underlying connection pool, you run into additional issues. -
Users often need different instances rather than a single bean.
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).
To register hypermedia-based message converters, add the following to your code:
@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.