1. Обзор
В этом уроке мы рассмотрим различные стратегии, доступные для обработки ошибок в проекте Spring WebFlux , проходя через практический пример.
Мы также укажем, где может быть выгодно использовать одну стратегию вместо другой, и в конце приведем ссылку на полный исходный код.
2. Настройка примера
Настройка Maven такая же , как и в нашей предыдущей статье, в которой содержится введение в Spring Web flux.
В нашем примере мы будем использовать конечную точку RESTful, которая принимает имя пользователя в качестве параметра запроса и возвращает “Hello username” в результате.
Во-первых, давайте создадим функцию маршрутизатора, которая направляет запрос /hello к методу с именем handleRequest в переданном обработчике:
@Bean public RouterFunctionrouteRequest(Handler handler) { return RouterFunctions.route(RequestPredicates.GET("/hello") .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), handler::handleRequest); }
Далее мы определим метод handleRequest () , который вызывает метод sayHello() и находит способ включить/вернуть его результат в тело ServerResponse :
public MonohandleRequest(ServerRequest request) { return //... sayHello(request) //... }
Наконец, метод sayHello () – это простой служебный метод, который объединяет строку “Hello” | и имя пользователя:
private MonosayHello(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 MonohandleRequest(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 MonohandleRequest(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 MonohandleRequest(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 MonohandleRequest(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 MapgetErrorAttributes(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 RouterFunctiongetRoutingFunction( 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 .