помещение
мы обычно используемSpringMVC
, покаDispatcherServlet
Обработка запросов может осуществляться через@ControllerAdvice
а также@ExceptionHandler
Настройте логику обработки различных типов исключений.ResponseEntityExceptionHandler
а такжеDefaultHandlerExceptionResolver
, базовый принцип очень прост, то есть при возникновении исключения ищите обработчик исключения, который уже существует в контейнере, и сопоставьте соответствующий тип исключения, и используйте указанный обработчик исключения для возврата результата после успешного сопоставления.Response
Если обработчик исключений по умолчанию не может быть найден, для итогового результата будет использоваться обработчик исключений по умолчанию.
SpringMVC
Пользовательская система исключений, представленная вSpring-WebFlux
Это не относится к , на самом деле, причина очень проста, базовые работающие контейнеры двух не совпадают.WebExceptionHandler
даSpring-WebFlux
Интерфейс верхнего уровня обработчика исключений, поэтому трассировка к подклассам может быть прослежена доDefaultErrorWebExceptionHandler
даSpring Cloud Gateway
Глобальный обработчик исключений для класса конфигурации:ErrorWebFluxAutoConfiguration
.
Зачем настраивать обработку исключений
Сначала нарисуйте воображаемую, но близкую к реальной архитектурную схему и определите роль шлюза:
Роль шлюза во всей архитектуре:
- Направляйте запросы от серверного приложения к серверному приложению.
- Ответ (агрегированного) внутреннего приложения перенаправляется серверному приложению.
Предполагая, что служба шлюза всегда работает нормально:
Для первого пункта, предполагая, что серверное приложение не может выйти в онлайн гладко и без потерь, будет определенная вероятность того, что шлюз направит запрос на некоторые серверные «узлы-зомби» (когда запрос маршрутизируется, приложение лучше перезапустить или просто остановить)», в это время маршрутизация не выдаст исключение, и общий случай — отказ в соединении.
Что касается второго пункта, при условии, что серверное приложение неправильно обрабатывает исключение, информация об исключении должна быть перенаправлена обратно серверному приложению через шлюз.Теоретически в этом случае исключения не произойдет.
На самом деле остается еще третья скрытая проблема.Если шлюз не только берет на себя функцию маршрутизации, но и включает в себя такие функции, как аутентификация и ограничение тока.Если эти функции разрабатываются без должной обработки перехвата исключений или даже самой логики есть ошибки, возможно, что исключение не перехватывается и не обрабатывается нормально, оставляя обработчик исключений по умолчаниюDefaultErrorWebExceptionHandler
, логика обработки обработчика исключений по умолчанию может не соответствовать ожидаемым результатам.
Как настроить обработку исключений
Сначала мы можем взглянуть на класс конфигурации обработчика исключений по умолчанию.ErrorWebFluxAutoConfiguration
:
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<ViewResolver> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.orderedStream()
.collect(Collectors.toList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(
ErrorAttributes errorAttributes) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(
errorAttributes, this.resourceProperties,
this.serverProperties.getError(), this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class,
search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(
this.serverProperties.getError().isIncludeException());
}
}
Обратите внимание на два экземпляра BeanErrorWebExceptionHandler
а такжеDefaultErrorAttributes
все использовано@ConditionalOnMissingBean
Аннотации, то есть мы можем переопределить их с помощью пользовательских реализаций. Сначала настройте одинCustomErrorWebFluxAutoConfiguration
(КромеErrorWebExceptionHandler
Пользовательская реализация, другая прямая копияErrorWebFluxAutoConfiguration
):
@Configuration
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class CustomErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public CustomErrorWebFluxAutoConfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<ViewResolver> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.orderedStream()
.collect(Collectors.toList());
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class,
search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
// TODO 这里完成自定义ErrorWebExceptionHandler实现逻辑
return null;
}
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
}
ErrorWebExceptionHandler
реализации, вы можете напрямую обратиться кDefaultErrorWebExceptionHandler
, или даже прямое наследованиеDefaultErrorWebExceptionHandler
, вы можете переопределить соответствующий метод. Здесь информация об исключении непосредственно инкапсулируется в следующем форматеResponse
Вернуть и, наконец, нужно отобразить в формате JSON:
{
"code": 200,
"message": "描述信息",
"path" : "请求路径",
"method": "请求方法"
}
нам нужно проанализироватьDefaultErrorWebExceptionHandler
Некоторый исходный код в:
// 封装异常属性
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace);
}
// 渲染异常Response
protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.ALL);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
return ServerResponse.status(getHttpStatus(error))
.contentType(MediaType.APPLICATION_JSON_UTF8)
.body(BodyInserters.fromObject(error));
}
// 返回路由方法基于ServerResponse的对象
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
}
// HTTP响应状态码的封装,原来是基于异常属性的status属性进行解析的
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
int statusCode = (int) errorAttributes.get("status");
return HttpStatus.valueOf(statusCode);
}
Определите три точки:
- Объект, который окончательно инкапсулируется в тело ответа, поступает из
DefaultErrorWebExceptionHandler#getErrorAttributes()
, и результатMap<String, Object>
Последовательность байтов, в которую преобразуется экземпляр. - оригинальный
RouterFunction
Реализация поддерживает возврат только в формате HTML, нам нужно изменить его на возврат в формате JSON (или поддерживать возврат во всех форматах). -
DefaultErrorWebExceptionHandler#getHttpStatus()
Это инкапсуляция кода состояния ответа, исходная логика основана на атрибуте исключенияgetErrorAttributes()
Атрибут состояния анализируется.
индивидуальныеJsonErrorWebExceptionHandler
следующее:
public class JsonErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonErrorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties,
ErrorProperties errorProperties,
ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
// 这里其实可以根据异常类型进行定制化逻辑
Throwable error = super.getError(request);
Map<String, Object> errorAttributes = new HashMap<>(8);
errorAttributes.put("message", error.getMessage());
errorAttributes.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
errorAttributes.put("method", request.methodName());
errorAttributes.put("path", request.path());
return errorAttributes;
}
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
// 这里其实可以根据errorAttributes里面的属性定制HTTP响应码
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
класс конфигурацииCustomErrorWebFluxAutoConfiguration
Добавить кJsonErrorWebExceptionHandler
:
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
JsonErrorWebExceptionHandler exceptionHandler = new JsonErrorWebExceptionHandler(
errorAttributes,
resourceProperties,
this.serverProperties.getError(),
applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
Очень просто, здесь аномальный код состояния ответа HTTP унифицирован какHttpStatus.INTERNAL_SERVER_ERROR(500)
, не так много вещей, которые нужно преобразовать, если вы понимаете контекстную логику исходной обработки исключений.
контрольная работа
тестовый сценарий один: Запускайте только шлюз и напрямую вызывайте нижестоящую службу, если нижестоящая служба не запущена:
curl http://localhost:9090/order/host
// 响应结果
{"path":"/order/host","code":500,"message":"Connection refused: no further information: localhost/127.0.0.1:9091","method":"GET"}
Второй тестовый сценарий: подчиненная служба запускается и вызывается нормально, а сам шлюз выдает исключение.
Примените настраиваемый глобальный фильтр на шлюзе и специально создайте исключение:
@Component
public class ErrorGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
int i = 1/0;
return chain.filter(exchange);
}
}
curl http://localhost:9090/order/host
// 响应结果
{"path":"/order/host","code":500,"message":"/ by zero","method":"GET"}
Результат ответа соответствует настроенной логике, а журнал в фоновом режиме также печатает соответствующий стек исключений.
резюме
Автор всегда считал, что аномальная классификация и обработка в соответствии с классификацией — очень важная часть проекта. В системе, отвечающей за мою компанию, автор настаивает на реализации классификации и захвата исключений, в основном для того, чтобы различать исключения, которые можно повторить для компенсации, и исключения, которые нельзя повторить и требуют своевременного предупреждения, чтобы можно было настроить логику самовосстановления для восстанавливаемые исключения и исключения, которые не могут быть восстановлены, могут быть настроены Своевременное раннее предупреждение и вмешательство человека. так,Spring Cloud Gateway
Стек также должен изучить собственную логику обработки исключений.
Оригинальная ссылка
- Страницы на гитхабе:woohoo.throwable.club/2019/05/11/…
- Кодовая страница:кинутьable.coding.what/2019/05/11/…
(Конец этой статьи c-1-d e-a-20190511)
Технический публичный аккаунт ("Throwable Digest"), который время от времени выкладывает оригинальные технические статьи автора (никогда не занимайтесь плагиатом и не перепечатывайте):