Рубрики
Без рубрики

Обработка ошибок в весеннем веб – потоке

Взгляните на различные методы изящной обработки ошибок в Spring Web flux.

Автор оригинала: baeldung.

1. Обзор

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

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

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

Настройка Maven такая же , как и в нашей предыдущей статье, в которой содержится введение в Spring Web flux.

В нашем примере мы будем использовать конечную точку RESTful, которая принимает имя пользователя в качестве параметра запроса и возвращает “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” | и имя пользователя:

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

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

Однако если мы вызовем одну и ту же конечную точку без указания имени пользователя, например “/hello”, это вызовет исключение.

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

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

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

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

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

Мы можем использовать on Error Return() для возврата статического значения по умолчанию всякий раз, когда возникает ошибка:

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

Здесь мы возвращаем статическое “Привет, незнакомец” всякий раз, когда функция конкатенации sayHello() выдает исключение.

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

Есть три способа, которые мы можем использовать on Error Resume для обработки ошибок:

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

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

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

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

Далее, давайте вызовем резервный метод при возникновении ошибки :

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

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

Последний вариант с использованием on Error Resume() заключается в том, чтобы перехватить, обернуть и повторно выдать ошибку , например, в качестве исключения Name Required:

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

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

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

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

Однако мы можем решить обрабатывать наши ошибки веб-потока на глобальном уровне. Для этого нам нужно сделать всего два шага:

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

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

public class GlobalErrorAttributes extends DefaultErrorAttributes{
    
    @Override
    public Map getErrorAttributes(ServerRequest request, 
      ErrorAttributeOptions options) {
        Map map = super.getErrorAttributes(
          request, options);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }

}

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

Далее, давайте реализуем глобальный обработчик ошибок . Для этого Spring предоставляет удобный Abstract Error WebExceptionHandler класс, который мы можем расширить и реализовать при обработке глобальных ошибок:

@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, 
         ErrorAttributeOptions.defaults());

       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON)
         .body(BodyInserters.fromValue(errorPropertiesMap));
    }
}

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

Объект error Attributes будет точной копией объекта, который мы передаем в конструкторе обработчика веб-исключений. В идеале это должен быть наш настроенный класс атрибутов ошибок.

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

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

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

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

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

Как и было обещано, полный исходный код, сопровождающий статью, доступен на GitHub .