Spring 5 WebClient

1. Überblick

In diesem Artikel zeigen wir den WebClient - einen reaktiven Webclient, der im Frühjahr 5 eingeführt wird.

Wir werden uns auch den WebTestClient ansehen - einen WebClient , der für Tests entwickelt wurde.

2. Was ist der WebClient ?

Einfach ausgedrückt ist WebClient eine Schnittstelle, die den Haupteinstiegspunkt für die Durchführung von Webanforderungen darstellt.

Es wurde als Teil des Spring Web Reactive-Moduls erstellt und wird in diesen Szenarien den klassischen RestTemplate ersetzen. Der neue Client ist eine reaktive, nicht blockierende Lösung, die über das HTTP/1.1-Protokoll arbeitet.

Schließlich verfügt die Schnittstelle über eine einzige Implementierung - die DefaultWebClient -Klasse -, mit der wir arbeiten.

3. Abhängigkeiten

Da wir eine Spring Boot-Anwendung verwenden, benötigen wir https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22spring-boot-starter-webflux%22%20AND%20g%3A % 22org.springframework.boot% 22[ spring-boot-starter-webflux ]Abhängigkeit sowie the Reactor-Projekt.

3.1. Bauen mit Maven

Fügen wir der pom.xml -Datei die folgenden Abhängigkeiten hinzu:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
    <groupId>org.projectreactor</groupId>
    <artifactId>reactor-spring</artifactId>
    <version>1.0.1.RELEASE</version>
</dependency>

3.2. Bauen mit Gradle

Mit Gradle müssen wir die folgenden Einträge zur build.gradle -Datei hinzufügen:

dependencies {
    compile 'org.springframework.boot:spring-boot-starter-webflux'
    compile 'org.projectreactor:reactor-spring:1.0.1.RELEASE'
}

4. Mit dem WebClient arbeiten

Um richtig mit dem Kunden arbeiten zu können, müssen wir wissen, wie

  • eine Instanz erstellen

  • eine Anfrage stellen

  • handhabt die Antwort

4.1. WebClient -Instanz erstellen

Es stehen drei Optionen zur Auswahl. Beim ersten Objekt wird ein WebClient -Objekt mit Standardeinstellungen erstellt:

WebClient client1 = WebClient.create();

Die zweite Alternative ermöglicht das Initiieren einer WebClient -Instanz mit einem bestimmten Basis-URI:

WebClient client2 = WebClient.create("http://localhost:8080");

Der letzte (und der fortschrittlichste) Weg besteht im Erstellen eines Clients mithilfe der Klasse DefaultWebClientBuilder , die eine vollständige Anpassung ermöglicht:

WebClient client3 = WebClient
  .builder()
    .baseUrl("http://localhost:8080")
    .defaultCookie("cookieKey", "cookieValue")
    .defaultHeader(HttpHeaders.CONTENT__TYPE, MediaType.APPLICATION__JSON__VALUE)
    .defaultUriVariables(Collections.singletonMap("url", "http://localhost:8080"))
  .build();

4.2. Anfrage vorbereiten

Zuerst müssen wir eine HTTP-Methode einer Anforderung angeben, indem wir die method (HttpMethod-Methode) aufrufen oder deren Verknüpfungsmethoden aufrufen, z. B. get , post , delete :

WebClient.UriSpec<WebClient.RequestBodySpec> request1 = client3.method(HttpMethod.POST);
WebClient.UriSpec<WebClient.RequestBodySpec> request2 = client3.post();

Der nächste Schritt besteht darin, eine URL anzugeben. Wir können es an die uri -API übergeben - als String - oder java.net.URL -Instanz:

WebClient.RequestBodySpec uri1 = client3
  .method(HttpMethod.POST)
  .uri("/resource");

WebClient.RequestBodySpec uri2 = client3
  .post()
  .uri(URI.create("/resource"));

Im weiteren Verlauf können wir einen Anforderungstext, Inhaltstyp, Länge, Cookies oder Header festlegen, wenn dies erforderlich ist.

Wenn wir zum Beispiel einen Anfragetext festlegen möchten, gibt es zwei Möglichkeiten: Befüllen mit einem BodyInserter oder Delegieren dieser Arbeit an einen Publisher :

WebClient.RequestHeadersSpec requestSpec1 = WebClient
  .create()
  .method(HttpMethod.POST)
  .uri("/resource")
  .body(BodyInserters.fromPublisher(Mono.just("data")), String.class);

WebClient.RequestHeadersSpec<?> requestSpec2 = WebClient
  .create("http://localhost:8080")
  .post()
  .uri(URI.create("/resource"))
  .body(BodyInserters.fromObject("data"));
  • Das BodyInserter ist eine Schnittstelle, die dafür verantwortlich ist, einen ReactiveHttpOutputMessage -Body mit einer bestimmten Ausgabenachricht und einem beim Einfügen verwendeten Kontext zu füllen. ** Ein Publisher ist eine reaktive Komponente, die für die Bereitstellung einer möglicherweise unbegrenzten Anzahl von Elementen in einer Reihenfolge verantwortlich ist.

Der zweite Weg ist die body -Methode, eine Abkürzung für die ursprüngliche __body (BodyInserter Inserter) -Methode.

Um diesen Vorgang des Befüllens eines BodyInserter zu erleichtern, gibt es eine BodyInserters__-Klasse, die eine Reihe nützlicher Hilfsmethoden enthält:

BodyInserter<Publisher<String>, ReactiveHttpOutputMessage> inserter1 = BodyInserters
  .fromPublisher(Subscriber::onComplete, String.class);

Es ist auch mit einer MultiValueMap möglich:

LinkedMultiValueMap map = new LinkedMultiValueMap();

map.add("key1", "value1");
map.add("key2", "value2");

BodyInserter<MultiValueMap, ClientHttpRequest> inserter2
 = BodyInserters.fromMultipartData(map);

Oder mit einem einzelnen Objekt:

BodyInserter<Object, ReactiveHttpOutputMessage> inserter3
 = BodyInserters.fromObject(new Object());

Nachdem wir den Körper festgelegt haben, können wir Header, Cookies und akzeptable Medientypen festlegen. Werte werden bei der Instantiierung des Clients hinzugefügt.

Darüber hinaus gibt es eine zusätzliche Unterstützung für die am häufigsten verwendeten Header wie __ "Wenn-Keine-Übereinstimmung", "Wenn-Modifiziert-Seit", "Akzeptieren", "Akzeptieren-Zeichensatz".

Hier ein Beispiel, wie diese Werte verwendet werden können:

WebClient.ResponseSpec response1 = uri1
  .body(inserter3)
    .header(HttpHeaders.CONTENT__TYPE, MediaType.APPLICATION__JSON__VALUE)
    .accept(MediaType.APPLICATION__JSON, MediaType.APPLICATION__XML)
    .acceptCharset(Charset.forName("UTF-8"))
    .ifNoneMatch("** ")
    .ifModifiedSince(ZonedDateTime.now())
  .retrieve();

4.3. Antwort bekommen

Die letzte Phase ist das Senden der Anfrage und das Empfangen einer Antwort. Dies kann entweder mit der Methode exchange oder mit der Methode retrieve erfolgen.

Sie unterscheiden sich in den Rückgabetypen. Die exchange -Methode stellt eine ClientResponse zusammen mit ihren Statusheatern bereit, während die retrieve -Methode der kürzeste Weg zum direkten Abrufen eines Hauptteils ist:

String response2 = request1.exchange()
  .block()
  .bodyToMono(String.class)
  .block();
String response3 = request2
  .retrieve()
  .bodyToMono(String.class)
  .block();

Beachten Sie die bodyToMono -Methode, die eine WebClientException auslöst, wenn der Statuscode 4xx (Clientfehler) oder 5xx (Serverfehler) ist. Wir haben die block -Methode für __Mono __s verwendet, um tatsächliche Daten zu abonnieren und abzurufen, die mit der Antwort gesendet wurden.

5. Arbeiten mit dem WebTestClient

Der WebTestClient ist der Haupteinstiegspunkt für das Testen von WebFlux-Serverendpunkten. Es hat eine sehr ähnliche API wie der WebClient und delegiert den Großteil der Arbeit an eine interne WebClient -Instanz, die sich hauptsächlich auf die Bereitstellung eines Testkontexts konzentriert. Die DefaultWebTestClient -Klasse ist eine Implementierung einer einzelnen Schnittstelle.

Der zu testende Client kann an einen realen Server gebunden sein oder mit bestimmten Controllern oder Funktionen arbeiten. Um End-to-End-Integrationstests mit tatsächlichen Anforderungen an einen laufenden Server durchzuführen, können Sie die Methode bindToServer verwenden:

WebTestClient testClient = WebTestClient
  .bindToServer()
  .baseUrl("http://localhost:8080")
  .build();

Wir können eine bestimmte RouterFunction testen, indem Sie sie an die bindToRouterFunction -Methode übergeben:

RouterFunction function = RouterFunctions.route(
  RequestPredicates.GET("/resource"),
  request -> ServerResponse.ok().build()
);

WebTestClient
  .bindToRouterFunction(function)
  .build().get().uri("/resource")
  .exchange()
  .expectStatus().isOk()
  .expectBody().isEmpty();

Dasselbe Verhalten kann mit der bindToWebHandler -Methode erreicht werden, die eine WebHandler -Instanz benötigt:

WebHandler handler = exchange -> Mono.empty();
WebTestClient.bindToWebHandler(handler).build();

Eine interessantere Situation tritt auf, wenn wir die bindToApplicationContext -Methode verwenden. Es benötigt einen ApplicationContext , analysiert den Kontext für Controller-Beans und @ EnableWebFlux -Konfigurationen.

Wenn wir eine Instanz von ApplicationContext einfügen, kann ein einfacher Code-Ausschnitt so aussehen:

@Autowired
private ApplicationContext context;

WebTestClient testClient = WebTestClient.bindToApplicationContext(context)
  .build();

Ein kürzerer Ansatz wäre die Bereitstellung eines Arrays von Controllern, die mit der bindToController -Methode getestet werden sollen. Angenommen, wir haben eine Controller -Klasse und wir haben sie in eine benötigte Klasse eingefügt, können wir schreiben:

@Autowired
private Controller controller;

WebTestClient testClient = WebTestClient.bindToController(controller).build();

Nach dem Erstellen eines WebTestClient -Objekts ähneln alle folgenden Operationen in der Kette der WebClient -Methode bis zur exchange -Methode (eine Möglichkeit, eine Antwort zu erhalten), die die WebTestClient.ResponseSpec -Schnittstelle für nützliche Methoden wie den expectStatus bereitstellt expectBody , expectHeader :

WebTestClient
  .bindToServer()
    .baseUrl("http://localhost:8080")
    .build()
    .post()
    .uri("/resource")
  .exchange()
    .expectStatus().isCreated()
    .expectHeader().valueEquals("Content-Type", "application/json")
    .expectBody().isEmpty();

6. Fazit

In diesem Lernprogramm haben wir einen neuen, verbesserten Spring-Mechanismus für die Abgabe von Anforderungen auf der Clientseite betrachtet - die WebClient -Klasse.

Außerdem haben wir uns die Vorteile angesehen, die es bietet, wenn es um die gesamte Anforderungsverarbeitung geht.

Alle in diesem Artikel erwähnten Code-Snippets finden Sie unter ur GitHub-Repository .