Fehler in Spring WebFlux behandeln

Fehlerbehandlung in Spring WebFlux

1. Überblick

In diesem Tutorial wirdwe’ll look at various strategies available for handling errors in a Spring WebFlux projectbeim Durchlaufen eines praktischen Beispiels angegeben.

Wir werden auch darauf hinweisen, wo es vorteilhaft sein kann, eine Strategie gegenüber einer anderen zu verwenden, und am Ende einen Link zum vollständigen Quellcode bereitstellen.

2. Einrichten des Beispiels

Das Maven-Setup entspricht demprevious article, das eine Einführung in Spring Webflux bietet.

In unserem Beispiel ergibt sichwe’ll usea RESTful endpoint that takes a username as a query parameter and returns “Hello username”.

Zunächst erstellen wir eine Routerfunktion, die die Anforderung von/helloan eine Methode mit dem NamenhandleRequest im übergebenen Handler weiterleitet:

@Bean
public RouterFunction routeRequest(Handler handler) {
    return RouterFunctions.route(RequestPredicates.GET("/hello")
      .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)),
        handler::handleRequest);
    }

Als nächstes definieren wir diehandleRequest()-Methode, die diesayHello()-Methode aufruft und einen Weg findet, das Ergebnis in denServerResponse-Körper aufzunehmen / zurückzugeben:

public Mono handleRequest(ServerRequest request) {
    return
      //...
        sayHello(request)
      //...
}

Schließlich ist diesayHello()-Methode eine einfache Dienstprogrammmethode, die die "Hello"String und den Benutzernamen verkettet:

private Mono sayHello(ServerRequest request) {
    //...
    return Mono.just("Hello, " + request.queryParam("name").get());
    //...
}

Solange ein Benutzername als Teil unserer Anfrage vorhanden ist, z. Wenn der Endpunkt als“/hello?username=Tonni “bezeichnet wird, funktioniert dieser Endpunkt immer korrekt.

if we call the same endpoint without specifying a username e.g. “/hello”, it will throw an exception.

Im Folgenden sehen wir uns an, wo und wie wir unseren Code neu organisieren können, um diese Ausnahme in WebFlux zu behandeln.

3. Behandlung von Fehlern auf funktionaler Ebene

In die APIsMono undFlux sind zwei Schlüsseloperatoren integriert, um Fehler auf Funktionsebene zu behandeln.

Lassen Sie uns sie und ihre Verwendung kurz untersuchen.

3.1. Behandlung von Fehlern mitonErrorReturn

We can use onErrorReturn() to return a static default value, wenn ein Fehler auftritt:

public Mono handleRequest(ServerRequest request) {
    return sayHello(request)
      .onErrorReturn("Hello Stranger")
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s));
}

Hier geben wir einen statischen "Hello Stranger" zurück, wenn die fehlerhafte VerkettungsfunktionsayHello() eine Ausnahme auslöst.

3.2. Behandlung von Fehlern mitonErrorResume

Es gibt drei Möglichkeiten, wie wironErrorResume verwenden können, um Fehler zu behandeln:

  • Berechnen Sie einen dynamischen Fallback-Wert

  • Führen Sie einen alternativen Pfad mit einer Fallback-Methode aus

  • Fangen Sie einen Fehler ab, wickeln Sie ihn um und werfen Sie ihn erneut, z. als benutzerdefinierte Geschäftsausnahme

Mal sehen, wie wir einen Wert berechnen können:

public Mono handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
          .syncBody(s))
        .onErrorResume(e -> Mono.just("Error " + e.getMessage())
          .flatMap(s -> ServerResponse.ok()
            .contentType(MediaType.TEXT_PLAIN)
            .syncBody(s)));
}

Hier geben wir eine Zeichenfolge zurück, die aus der dynamisch erhaltenen Fehlermeldung besteht, die an die Zeichenfolge "Fehler" angehängt wird, wennsayHello() eine Ausnahme auslöst.

Als nächstes wollen wircall a fallback method when an error occurs:

public Mono handleRequest(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s))
      .onErrorResume(e -> sayHelloFallback()
      .flatMap(s ->; ServerResponse.ok()
      .contentType(MediaType.TEXT_PLAIN)
      .syncBody(s)));
}

Hier rufen wir die alternative MethodesayHelloFallback() auf, wennsayHello() eine Ausnahme auslöst.

Die letzte Option unter Verwendung vononErrorResume() istcatch, wrap, and re-throw an error, z. alsNameRequiredException:

public Mono handleRequest(ServerRequest request) {
    return ServerResponse.ok()
      .body(sayHello(request)
      .onErrorResume(e -> Mono.error(new NameRequiredException(
        HttpStatus.BAD_REQUEST,
        "username is required", e))), String.class);
}

Hier lösen wir eine benutzerdefinierte Ausnahme mit der Meldung "Benutzername ist erforderlich" aus, wennsayHello() eine Ausnahme auslöst.

4. Fehlerbehandlung auf globaler Ebene

Bisher haben sich alle von uns vorgestellten Beispiele mit der Fehlerbehandlung auf funktionaler Ebene befasst.

We can, however, opt to handle our WebFlux errors at a global level. Dazu müssen wir nur zwei Schritte ausführen:

  • Passen Sie die globalen Fehlerantwortattribute an

  • Implementieren Sie den Global Error Handler

Die Ausnahme, die unser Handler auslöst, wird automatisch in einen HTTP-Status und einen JSON-Fehlertext übersetzt. Um diese anzupassen, können wir einfachextend the DefaultErrorAttributes class und diegetErrorAttributes()-Methode überschreiben:

public class GlobalErrorAttributes extends DefaultErrorAttributes{

    @Override
    public Map getErrorAttributes(ServerRequest request,
      boolean includeStackTrace) {
        Map map = super.getErrorAttributes(
          request, includeStackTrace);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }

}

Hier möchten wir den Status:BAD_REQUEST und die Meldung: "username is required" als Teil der Fehlerattribute zurückgeben, wenn eine Ausnahme auftritt.

Als nächstes wollen wirimplement the Global Error Handler. Zu diesem Zweck bietet Spring eine praktischeAbstractErrorWebExceptionHandler-Klasse, die wir bei der Behandlung globaler Fehler erweitern und implementieren können:

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends
    AbstractErrorWebExceptionHandler {

    // constructors

    @Override
    protected RouterFunction getRoutingFunction(
      ErrorAttributes errorAttributes) {

        return RouterFunctions.route(
          RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono renderErrorResponse(
       ServerRequest request) {

       Map errorPropertiesMap = getErrorAttributes(request, false);

       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON_UTF8)
         .body(BodyInserters.fromObject(errorPropertiesMap));
    }
}

In diesem Beispiel setzen wir die Reihenfolge unserer globalen Fehlerbehandlungsroutine auf -2. Dies istgive it a higher priority than the DefaultErrorWebExceptionHandler, was bei@Order(-1). registriert ist

DaserrorAttributes-Objekt ist die exakte Kopie des Objekts, das wir im Konstruktor des Web Exception Handler übergeben. Dies sollte idealerweise unsere angepasste Fehlerattributklasse sein.

Dann geben wir eindeutig an, dass wir alle Fehlerbehandlungsanforderungen an dierenderErrorResponse()-Methode weiterleiten möchten.

Schließlich werden die Fehlerattribute abgerufen und in einen Serverantworttext eingefügt.

Daraufhin wird eine JSON-Antwort mit Details zum Fehler, zum HTTP-Status und zur Ausnahmemeldung für Computer-Clients erstellt. Für Browser-Clients verfügt es über einen "Whitelabel" -Fehlerbehandler, mit dem dieselben Daten im HTML-Format gerendert werden. Dies kann natürlich angepasst werden.

5. Fazit

In diesem Artikel haben wir uns mit verschiedenen Strategien zum Behandeln von Fehlern in einem Spring WebFlux-Projekt befasst und aufgezeigt, wo es vorteilhaft sein kann, eine Strategie einer anderen vorzuziehen.

Wie versprochen ist der vollständige Quellcode, der dem Artikel beiliegt,over on GitHub verfügbar.