Глобальная обработка исключений для Spring Boot 2 Webflux

Микросервисы Spring

Эта статья впервые будет проанализировать механизм обработки исключений SPRINGMVC до 5 весны, затем пружин Global 2 WebFlux в основном на механизме обработки загрузки загрузки.

Обработка исключений в SpringMVC

В Spring есть три способа унифицировать обработку исключений, а именно:

  • использовать@ExceptionHandlerаннотация
  • выполнитьHandlerExceptionResolverинтерфейс
  • использовать@controlleradviceаннотация

использовать@ExceptionHandlerаннотация

Используется для захвата локального метода в том же классе контроллера, что и метод, вызывающий исключение:

@Controller
public class BuzController {

    @ExceptionHandler({NullPointerException.class})
    public String exception(NullPointerException e) {
        System.out.println(e.getMessage());
        e.printStackTrace();
        return "null pointer exception";
    }

    @RequestMapping("test")
    public void test() {
        throw new NullPointerException("出错了!");
    }
}

Приведенный выше код реализован дляBuzControllerброшенныйNullPointerExceptionException поймает локальное исключение и вернет указанный контент.

выполнитьHandlerExceptionResolverинтерфейс

путем реализацииHandlerExceptionResolverИнтерфейс, здесь мы передаем наследованиеSimpleMappingExceptionResolverКласс реализации (HandlerExceptionResolverРеализация, которая позволяет сопоставлять имена классов исключений с именами представлений либо для заданного набора обработчиков, либо для всех обработчиков в DispatcherServlet) Определите глобальные исключения:

@Component
public class CustomMvcExceptionHandler extends SimpleMappingExceptionResolver {

    private ObjectMapper objectMapper;

    public CustomMvcExceptionHandler() {
        objectMapper = new ObjectMapper();
    }

    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
                                         Object o, Exception ex) {
        response.setStatus(200);
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        response.setCharacterEncoding("UTF-8");
        response.setHeader("Cache-Control", "no-cache, must-revalidate");
        Map<String, Object> map = new HashMap<>();
        if (ex instanceof NullPointerException) {
            map.put("code", ResponseCode.NP_EXCEPTION);
        } else if (ex instanceof IndexOutOfBoundsException) {
            map.put("code", ResponseCode.INDEX_OUT_OF_BOUNDS_EXCEPTION);
        } else {
            map.put("code", ResponseCode.CATCH_EXCEPTION);
        }
        try {
            map.put("data", ex.getMessage());
            response.getWriter().write(objectMapper.writeValueAsString(map));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new ModelAndView();
    }
}

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

использовать@controlleradviceаннотация

@ControllerAdvice
public class ExceptionController {
    @ExceptionHandler(RuntimeException.class)
    public ModelAndView handlerRuntimeException(RuntimeException ex) {
        if (ex instanceof MaxUploadSizeExceededException) {
            return new ModelAndView("error").addObject("msg", "文件太大!");
        }
        return new ModelAndView("error").addObject("msg", "未知错误:" + ex);
    }

    @ExceptionHandler(Exception.class)
    public ModelAndView handlerMaxUploadSizeExceededException(Exception ex) {
        if (ex != null) {
            return new ModelAndView("error").addObject("msg", ex);
        }

        return new ModelAndView("error").addObject("msg", "未知错误:" + ex);

    }
}

Отличие от первого способа в том, чтоExceptionHandlerОпределение и перехват исключений можно расширить глобально.

Обработка исключений для Spring 5 Webflux

Webflux поддерживает аннотации mvc, что очень удобно, по сравнению с RouteFunction автоматическое сканирование и регистрация проще. Обработка исключений может следовать за ExceptionHandler. Следующая глобальная обработка исключений по-прежнему действительна для RestController.

@RestControllerAdvice
public class CustomExceptionHandler {
    private final Log logger = LogFactory.getLog(getClass());

    @ExceptionHandler(Exception.class)
    @ResponseStatus(code = HttpStatus.OK)
    public ErrorCode handleCustomException(Exception e) {
        logger.error(e.getMessage());
        return new ErrorCode("e","error" );
    }
}

Пример WebFlux

WebFlux предоставляет набор функциональных интерфейсов, которые можно использовать для достижения эффектов, подобных MVC. Начнем с двух часто используемых.

Вариант осуществления определения контроллера Обработка логического запроса, основные аспекты:

  • Логика обработки определения метода;
  • Затем используйте аннотацию @RequestMapping, чтобы определить, на какой URL-адрес отвечает этот метод.

В режиме функциональной разработки WebFlux мы используем HandlerFunction и RouterFunction для достижения двух вышеуказанных пунктов.

HandlerFunction

HandlerFunctionЭквивалентно конкретному методу обработки в контроллере, ввод — это запрос, а вывод — ответ, установленный в Mono:

    Mono<T> handle(ServerRequest var1);

В WebFlux запросы и ответы больше не являются ServletRequest и ServletResponse в WebMVC, а являются ServerRequest и ServerResponse. Последние представляют собой интерфейсы, используемые в реактивном программировании, они обеспечивают поддержку функций неблокировки и обратного давления, а также методы преобразования тел сообщений Http в реактивные типы Mono и Flux.

@Component
public class TimeHandler {
    public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
        String timeType = serverRequest.queryParam("type").get();
        //return ...
    }
}

Как определено вышеTimeHandlerВозвращает текущее время согласно параметрам запроса.

RouterFunction

RouterFunction, как следует из названия, маршрутизация, эквивалентная@RequestMapping, используемый для определения того, какой тип URL-адреса соответствует этому конкретному URL-адресу.HandlerFunction. Ввод - это запрос, вывод - в моноHandlerfunction:

Mono<HandlerFunction<T>> route(ServerRequest var1);

Для функций, которые мы хотим предоставить извне, мы определяем Route.

@Configuration
public class RouterConfig {
    private final TimeHandler timeHandler;

    @Autowired
    public RouterConfig(TimeHandler timeHandler) {
        this.timeHandler = timeHandler;
    }

    @Bean
    public RouterFunction<ServerResponse> timerRouter() {
        return route(GET("/time"), req -> timeHandler.getTime(req));
    }
}

Вы можете видеть, что запрос GET на доступ / время будет отправленTimeHandler::getTimeиметь дело с.

Обращение с исключениями на уровне функций

Если мы вызовем тот же адрес запроса без указания типа (типа) времени, например /time, будет выброшено исключение. API-интерфейсы Mono и Flux имеют два встроенных ключевых оператора для обработки ошибок на функциональном уровне.

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

Вы также можете использовать onErrorResume для обработки ошибок.Резервный метод определяется следующим образом:

Mono<T> onErrorResume(Function<? super Throwable, ? extends Mono<? extends T>> fallback);

Когда возникает ошибка, мы используем резервный метод для выполнения альтернативного пути:

@Component
public class TimeHandler {
    public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
        String timeType = serverRequest.queryParam("time").orElse("Now");
        return getTimeByType(timeType).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)));
    }

    private Mono<String> getTimeByType(String timeType) {
        String type = Optional.ofNullable(timeType).orElse(
                "Now"
        );
        switch (type) {
            case "Now":
                return Mono.just("Now is " + new SimpleDateFormat("HH:mm:ss").format(new Date()));
            case "Today":
                return Mono.just("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
            default:
                return Mono.empty();
        }
    }
}

В приведенной выше реализации всякий раз, когдаgetTimeByType()Когда вы сгенерируете исключение, мы выполним наше определение.fallbackметод. В дополнение к этому мы также можем перехватывать, оборачивать и повторно выдавать исключения, например, в качестве пользовательских бизнес-исключений:

    public Mono<ServerResponse> getTime(ServerRequest serverRequest) {
        String timeType = serverRequest.queryParam("time").orElse("Now");
        return ServerResponse.ok()
                .body(getTimeByType(timeType)
                        .onErrorResume(e -> Mono.error(new ServerException(new ErrorCode(HttpStatus.BAD_REQUEST.value(),
                                "timeType is required", e.getMessage())))), String.class);
    }

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

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

    public Mono<ServerResponse> getDate(ServerRequest serverRequest) {
        String timeType = serverRequest.queryParam("time").get();
        return getTimeByType(timeType)
                .onErrorReturn("Today is " + new SimpleDateFormat("yyyy-MM-dd").format(new Date()))
                .flatMap(s -> ServerResponse.ok()
                        .contentType(MediaType.TEXT_PLAIN).syncBody(s));
    }

глобальная обработка исключений

Приведенная выше конфигурация обрабатывает исключения на уровне метода.Как и глобальная обработка исключений для аннотированных контроллеров, режим функциональной разработки WebFlux также может обрабатывать глобальные исключения. Для этого нам просто нужно настроить глобальное свойство ответа на ошибку и реализовать глобальную логику обработки ошибок.

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

@Component
public class GlobalErrorAttributes extends DefaultErrorAttributes {

    public GlobalErrorAttributes() {
        super(false);
    }

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        return assembleError(request);
    }

    private Map<String, Object> assembleError(ServerRequest request) {
        Map<String, Object> errorAttributes = new LinkedHashMap<>();
        Throwable error = getError(request);
        if (error instanceof ServerException) {
            errorAttributes.put("code", ((ServerException) error).getCode().getCode());
            errorAttributes.put("data", error.getMessage());
        } else {
            errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR);
            errorAttributes.put("data", "INTERNAL SERVER ERROR");
        }
        return errorAttributes;
    }
    //...有省略
}

В приведенной выше реализации мы имеемServerExceptionобрабатывается специально, в зависимости от поступающихErrorCodeОбъект создает соответствующий ответ.

Далее давайте реализуем глобальный обработчик ошибок. Для этой цели Spring предоставляет удобныйAbstractErrorWebExceptionHandlerКласс для расширения и реализации при работе с глобальными ошибками:

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

	//构造函数
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(final ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono<ServerResponse> renderErrorResponse(final ServerRequest request) {

        final Map<String, Object> errorPropertiesMap = getErrorAttributes(request, true);

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

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

Объект errorAttributes будет точной копией объекта, который мы передали в конструкторе обработчика сетевых исключений. В идеале это должен быть наш собственный класс атрибутов ошибок. Затем мы ясно даем понять, что хотим направить все запросы на обработку ошибок в метод renderErrorResponse(). Наконец, мы получаем атрибуты ошибки и вставляем их в тело ответа сервера.

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

резюме

В этом документе говорилось о механизме обработки исключений SpringMVC до 5 Spring, унифицированной обработке исключений SpringMVC. Существует три способа: использование@ExceptionHandlerHandlerExceptionResolverинтерфейс, использование@controlleradviceАннотации; затем создайте веб-приложение через функциональный интерфейс WebFlux и объясните функциональный уровень и глобальный механизм обработки исключений Spring Boot 2 Webflux (для стиля Spring WebMVC, если вы пишете отзывчивые веб-сервисы на основе аннотаций, вы все равно можете использовать SpringMVC для унификации обработки исключений.

Примечание. Вторая половина этой статьи в основном переведена сWoohoo. Возьмите Arlington Terrier.com/spring-web ..

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

微信公众号

Ссылаться на

  1. Handling Errors in Spring WebFlux
  2. Быстро начните работу с помощью Spring WebFlux — отзывчивого волшебного инструмента Spring