Обработка ошибок в Spring WebFlux

Обработка ошибок в Spring WebFlux

1. обзор

В этом руководствеwe’ll look at various strategies available for handling errors in a Spring WebFlux project проходит через практический пример.

Мы также укажем, где может быть выгоднее использовать одну стратегию перед другой, и дадим ссылку на полный исходный код в конце.

2. Настройка примера

Настройка Maven такая же, как у нашегоprevious article, который дает введение в Spring Webflux.

В нашем примере в результатеwe’ll usea RESTful endpoint that takes a username as a query parameter and returns “Hello username”.

Во-первых, давайте создадим функцию маршрутизатора, которая направляет запрос/hello методу с именемhandleRequest в переданном обработчике:

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

Затем мы определим методhandleRequest(), который вызывает методsayHello() и находит способ включения / возврата его результата в телоServerResponse:

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

Наконец, методsayHello() - это простой служебный метод, который объединяет «Hello»String и имя пользователя:

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

Пока имя пользователя присутствует как часть нашего запроса, например если конечная точка вызывается как“/hello?username=Tonni “, то эта конечная точка всегда будет работать правильно.

Однакоif we call the same endpoint without specifying a username e.g. “/hello”, it will throw an exception.

Ниже мы рассмотрим, где и как мы можем реорганизовать наш код для обработки этого исключения в WebFlux.

3. Обработка ошибок на функциональном уровне

В APIMono иFlux встроены два ключевых оператора для обработки ошибок на функциональном уровне.

Давайте кратко рассмотрим их и их использование.

3.1. Обработка ошибок с помощьюonErrorReturn

We can use onErrorReturn() to return a static default value всякий раз, когда возникает ошибка:

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

Здесь мы возвращаем статическое «Hello Stranger» всякий раз, когда ошибочная функция конкатенацииsayHello() выдает исключение.

3.2. Обработка ошибок с помощьюonErrorResume

onErrorResume можно использовать для обработки ошибок тремя способами:

  • Вычислить динамическое запасное значение

  • Выполнить альтернативный путь с помощью резервного метода

  • Поймать, обернуть и повторно выдать ошибку, например, в качестве особого бизнес-исключения

Давайте посмотрим, как мы можем вычислить значение:

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)));
}

Здесь мы возвращаем строку, состоящую из динамически полученного сообщения об ошибке, добавляемого к строке «Error» всякий раз, когдаsayHello() вызывает исключение.

Теперь давайтеcall 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)));
}

Здесь мы вызываем альтернативный методsayHelloFallback() всякий раз, когдаsayHello() вызывает исключение.

Последний вариант использованияonErrorResume() - этоcatch, wrap, and re-throw an error, например. какNameRequiredException:

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);
}

Здесь мы генерируем настраиваемое исключение с сообщением: «username is required» всякий раз, когдаsayHello() вызывает исключение.

4. Обработка ошибок на глобальном уровне

До сих пор все представленные примеры касались обработки ошибок на функциональном уровне.

We can, however, opt to handle our WebFlux errors at a global level. Для этого нам нужно сделать всего два шага:

  • Настройте глобальные атрибуты ответа на ошибку

  • Реализуйте глобальный обработчик ошибок

Исключение, которое выдает наш обработчик, будет автоматически переведено в состояние HTTP и тело ошибки JSON. Чтобы настроить их, мы можем простоextend the DefaultErrorAttributes class и переопределить его методgetErrorAttributes():

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;
    }

}

Здесь мы хотим, чтобы статус:BAD_REQUEST и сообщение: «username is required» возвращались как часть атрибутов ошибки при возникновении исключения.

Теперь давайтеimplement the Global Error Handler. Для этого Spring предоставляет удобный классAbstractErrorWebExceptionHandler, который мы можем расширить и реализовать при обработке глобальных ошибок:

@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));
    }
}

В этом примере мы устанавливаем порядок нашего глобального обработчика ошибок на -2. Это доgive it a higher priority than the DefaultErrorWebExceptionHandler, который зарегистрирован в@Order(-1).

ОбъектerrorAttributes будет точной копией того, который мы передаем в конструктор Web Exception Handler. В идеале это должен быть наш настраиваемый класс атрибутов ошибок.

Затем мы четко заявляем, что хотим направлять все запросы обработки ошибок методуrenderErrorResponse().

Наконец, мы получаем атрибуты ошибки и вставляем их в тело ответа сервера.

Затем создается ответ JSON с подробными сведениями об ошибке, статусе HTTP и сообщении об исключении для машинных клиентов. Для клиентов браузера он имеет обработчик ошибок «whitelabel», который отображает те же данные в формате HTML. Это, конечно, можно настроить.

5. Заключение

В этой статье мы рассмотрели различные стратегии, доступные для обработки ошибок в проекте Spring WebFlux, и указали, где может быть выгодно использовать одну стратегию над другой.

Как и было обещано, доступен полный исходный код, прилагаемый к статьеover on GitHub.