TLDR;
Spring 6 brings HttpServiceProxyFactory
class allowing you to easily generate HTTP client based on interfaces and annotations.
Introduction
Before Spring 6.x, creating an HTTP client consisted of implementing each calls using either the old RestTemplate
, the reactive WebClient
, or the new RestClient
introduced in Spring 6.1.
This leads to a lot of boilerplate code.
Nevertheless, some alternatives exist and aim to ease the development of rest client such as OpenFEIGN or the Eclipse MicroProfile RestClient specification.
In its last major release, Spring introduced the HttpServiceProxyFactory
class as well as a new blocking rest client RestClient
(in 6.1).
Thanks to this factory, you can declare your client using a simple interface annotated with @HttpExchange
annotations (similar to Spring Data Repositories). The factory will then take care of generating an implementation of your interface.
Giving it a try
Let's consider the following beer API (based on Brewery OpenDB)
GET /api/v1/beer
GET /api/v1/beer/{beerId}
POST /api/v1/beer
In order to create a client, we need to create an interface and specify annotation in order to declare:
- Path
- HTTP verb
- Headers
- Cookies
- Path and Query parameters
package org.dev.example;
import org.springframework.web.service.annotation.*;
public interface BeerClient {
@GetExchange("/api/v1/beer")
public List<Beer> listBeers();
@GetExchange("/api/v1/beer/{beerId}")
public Beer getBeer(@PathVariable String beerId);
@PostExchange("/api/v1/beer")
public Beer createBeer(@RequestBody Beer beer);
}
It is possible to "simplify" paths using @HttpExchange
directly on the interface
package org.dev.example;
import org.springframework.web.service.annotation.*;
@HttpExchange(url = "api/v1/beer", contentType = "application/json")
public interface BeerClient {
@GetExchange
public List<Beer> listBeers();
@GetExchange("/{beerId}")
public Beer getBeer(@PathVariable String beerId);
@PostExchange
public Beer createBeer(@RequestBody Beer beer);
}
Once the interface is created, creating a new client consists of using HttpServiceProxyFactory
, setting the http client we would like to use and then creating the client implementation.
public BeerClient buildBeerRestClient(WebClient webClient) {
HttpServiceProxyFactory clientFactory = HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
return clientFactory.createClient(BeerClient.class);
}
This can be integrated in a bean using a simple configuration class:
@Configuration
public class BeerClientConfiguration() {
@Bean
public BeerClient buildBeerClient(WebClient.Builder webClientBuilder) {
WebClient webClient = webClientBuilder.build();
HttpServiceProxyFactory clientFactory =
HttpServiceProxyFactory.builder(WebClientAdapter.forClient(webClient)).build();
return clientFactory.createClient(BeerClient.class);
}
}
This bean can then be injected as any other bean.
Customizing the WebClient
Usually, in most client, we need to inject some headers, add authentication tokens, set loggers, ...
This can be done directly in the WebClient or RestClient (introduced in Spring 6.1) builders using defaultHeaders(...)
method or using filters (interceptors when using RestClient
or RestTemplate
). For example, you can easily register a filter that add an authentication header based on the currently logged in user.
What about non blocking types ?
Types such as Mono
or Flux
, can be directly specified as return type in the interface such as
package org.dev.example;
import org.springframework.web.service.annotation.*;
import reactor.core.publisher.*;
@HttpExchange(url = "api/v1/beer", contentType = "application/json")
public interface BeerClient {
@GetExchange
public Flux<Beer> listBeers();
@GetExchange("/{beerId}")
public Mono<Beer> getBeer(@PathVariable String beerId);
@PostExchange
public Mono<Beer> createBeer(@RequestBody Beer beer);
}
The HttpServiceProxyFactory
also supports returning ResponseEntity
type (which can also be combined with Mono
and Flux
).
What about testing ?
Testing can be done in different ways:
- By overriding the
exchangeFunction
of theWebClient
used by theHttpServiceProxyFactory
- If your client is exposed as bean, it can be mocked as any other bean by your favorite mocking library.
Top comments (0)