Blog

Reaktiver Webdienst-Client mit JAX-WS

Barend Garvelink

Aktualisiert Oktober 21, 2025
5 Minuten

XML-Webdienste sind nicht tot

Vor etwa einem Jahrzehnt waren SOAP und WSDL (XML Web Services) der letzte Schrei in der Unternehmenssoftware. Angetrieben von allen großen Anbietern, darunter IBM, Microsoft, Oracle und damals auch Sun Microsystems, sollte alles WSDL-Endpunkte bereitstellen und die Welt würde dadurch besser werden. Das hat sich nicht bewahrheitet: Das WS-*-Kartenhaus brach unter seinem eigenen Gewicht zusammen, XML wurde in der Gegenreaktion zu einem Schimpfwort und REST+JSON setzte sich als The One True Way™ der Darstellung von Webservices durch (nicht ohne eigene Probleme).

Als Data Engineer besteht ein großer Teil meiner Aufgabe darin, den Weg für Data Science zu ebnen. Das bedeutet, dass ich Daten einbringen muss. In manchen Fällen muss ich alte XML-Webdienste abfragen. Wenn es sich dabei um einfache WSDL mit einem guten XML-Schema für die Nutzdaten handelt und alle zusätzlichen Standards der WS-*-Ära vermieden werden, macht mir das gar nichts aus.

In Java ist die Standardmethode für den Zugriff auf XML-Webdienste (und deren Bereitstellung) JAX-WS.

Reaktive Programmierung

Functional Reactive Programming (FRP) ist eine relativ neue Entwicklung in der Art und Weise, wie wir Java-Backend-Dienste schreiben. Es würde den Rahmen dieses Artikels sprengen, wenn wir uns ausführlich damit befassen würden, was es ist und was es bedeutet. Dan Lew hat in seinem Blog eine gute Einführung in FRP geschrieben. Das FRP-Modell führt zu Programmcode, der seine Absichten sehr klar formuliert:

connectToOrderStream()
    .flatMap(o -> addCustomerInformation(o.getCustomerId()))
    .flatMap(o -> addShippingInformation(o.getShippingAddress()))
    .doOnNext(this::sendOrderToFulfillmentCenter)
    .doOnNext(this::sendOrderConfirmationEmail);

In traditionellem, prozeduralem Java-Code wären die fünf Hauptoperationen dieses Beispiels weit voneinander entfernt und von Unmengen von Protokollierungs-, Transformations- und Fehlerbehandlungscode umgeben. Dieser Code ist im reaktiven Modell immer noch vorhanden, aber er wurde in die Hilfsmethoden ausgelagert, um die oben gezeigte saubere Komposition zu erhalten. Die Komposition selbst kümmert sich um einige der Fehlerbehandlungspfade.

FRP lässt sich gut mit nicht-blockierender IO (auch bekannt als asynchrone IO) kombinieren, um Dienste zu erstellen, die nicht nur ihre Absicht klar zum Ausdruck bringen, sondern auch eine gute Leistung bieten, insbesondere was die Hardwareauslastung betrifft. Non-blocking IO ist keine neue Idee, aber es waren Nginx und Node.js, die sie im Java-Land wirklich bekannt gemacht haben, und es hat eine Weile gedauert, bis die Anwendungs-Frameworks aufgeholt haben.

Wie in Java üblich, gibt es mehr als eine Implementierung von FRP. Die ursprüngliche Implementierung auf der JVM war RxJava. Oracle fügte die Flow-API zu Java 9 hinzu. In jüngerer Zeit gibt es Project Reactor, das von Spring Framework in der neuen Spring WebFlux-API unterstützt wird.

Mein Kunde hat sich für Spring Framework entschieden, daher verwende ich für den Rest dieses Artikels Reactor. Die drei Frameworks sind sich ziemlich ähnlich, und das Codebeispiel sollte sich leicht übersetzen lassen.

JAX-WS und nicht-blockierende IO

Ich kann wsimport die WSDL-Datei und haben in wenigen Minuten einen funktionierenden Webdienst-Client. Blockierende IO (synchron) war der Standard in Java, als JAX-WS eingeführt wurde, und wsimport erzeugt nur einen blockierenden Client. Wir benötigen einen nicht-blockierenden Client.

JAX-WS erzeugt einen Nicht-Block-Client, wenn Sie eine Bindungsanpassung vornehmen:

<?xml version="1.0"?>
<jaxws:bindings
        wsdlLocation="https://api.myservice.example.com/?wsdl"
        xmlns_jaxws="http://java.sun.com/xml/ns/jaxws">
    <!-- https://stackoverflow.com/a/43560882/49489 -->
    <jaxws:enableAsyncMapping>true</jaxws:enableAsyncMapping>
</jaxws:bindings>

Wenn Sie das oben gezeigte Snippet als async_mapping.xml speichern, können Sie es wie folgt an wsimport im Parameter -b übergeben:

wsimport 
     -b async_mapping.xml 
     "https://api.myservice.example.com/?wsdl"

Der generierte Code enthält nun eine blockierende und zwei nicht blockierende Überladungen für jede WSDL-Operation. Der generierte Code folgt dieser Struktur:

public interface ExampleSoapPortType {
    Output           operation      (Input request);
    Response<Output> operationAsync (Input request);
    Future<?>        operationAsync (Input request, AsyncHandler<Output> asyncHandler);
}

Jede WSDL-Operation wird in drei Methoden umgewandelt:

  1. Die erste der drei Varianten ist die ursprüngliche, blockierende Implementierung.
  2. Die zweite Möglichkeit, Response<type> zu verwenden, ist für unsere Bedürfnisse ungeeignet. Das liegt daran, dass die Klasse Response<type> die Klasse java.util.concurrent.Future. Future ist zwar tatsächlich asynchron, aber eine blockierende API: Wenn Sie den Wert get() eingeben, muss der aufrufende Thread auf die Antwort warten.
  3. Die dritte Variante, die den AsyncHandler Callback verwendet, ist sowohl asynchron als auch nicht-blockierend. Dies ist derjenige, den wir brauchen. Wir können seinen Rückgabewert ignorieren und uns ausschließlich auf verlassen. Dieser Callback-Handler wird auf einem JAX-WS-Worker-Thread aufgerufen, wenn die Antwort eingetroffen ist.

Überbrückung von nicht blockierenden JAX-WS und Project Reactor

Das letzte Teil des Puzzles besteht darin, ein AsyncHandler zu erstellen, das in das Reactor-Programmiermodell passt. Dazu verwenden Sie Mono.create() (oder Flux.create()) und einen Konsumenten von MonoSink (oder FluxSink), wie im Abschnitt Producer Create in den Reactor-Dokumenten erläutert.

Ein reaktiver JAX-WS Client-Aufruf sieht daher wie folgt aus:

class Demo {
    public Mono<Output> operation(Input input) {
        return Mono.create(sink ->
           portType.operation(input, outputFuture -> {
               try {
                   sink.success(outputFuture.get());
               } catch (Exception e) {
                   sink.error(e);
               }
           })
        );
    }
}

Wenn wir eine Utility-Klasse für die Boilerplate verwenden, können wir das so vereinfachen, dass es wie folgt aussieht:

import static com.xebia.jaxws.reactive.example.ReactorAsyncHandler.into;

class Demo {
    public Mono<Output> operation(Input input) {
        return Mono.create(sink -> portType.operation(input, into(sink)));
    }
}

Das Dienstprogramm ReactorAsyncHandler

Hier ist die Gesamtheit dieser Utility-Klasse:

package com.xebia.jaxws.reactive.example;

import java.util.Iterator;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.xml.ws.AsyncHandler;
import reactor.core.publisher.MonoSink;

/**
 * Bridge JAX-WS Async API to Reactor.
 *
 * <h2>Usage:</h2>
 *
 * <pre>
 *     Mono.create(sink -> endpoint.method(argument, ReactorAsyncHandler.into(sink));
 * </pre>
 */
public class ReactorAsyncHandler {
  public static <T> AsyncHandler<T> into(MonoSink<T> sink) {
    return res -> {
      try {
        sink.success(res.get(1, TimeUnit.MILLISECONDS));
      } catch (InterruptedException | ExecutionException | TimeoutException e) {
        sink.error(e);
      }
    };
  }
}

Die Flux API kann auf dieselbe Weise unterstützt werden.

Zusammenfassung

XML-Webdienste gibt es immer noch, auch wenn sie nicht mehr so populär sind wie in der Vergangenheit. JAX-WS ist das Standardwerkzeug, um sie in Java abzufragen. JAX-WS ist ein Vorläufer von Reactor, der Implementierung des funktionalen reaktiven Programmiermodells von Spring Framework. Die Lücke zwischen JAX-WS und Reactor kann mit dem asynchronen Modus von JAX-WS und einem einfachen Adapter überbrückt werden, der in diesem Artikel vorgestellt wird.

Verfasst von

Barend Garvelink

Contact

Let’s discuss how we can support your journey.