springcloud gateway默认的异常处理
在ErrorWebFluxAutoConfiguration类中,可见默认的异常处理配置:
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
@ConditionalOnClass(WebFluxConfigurer.class)
@AutoConfigureBefore(WebFluxAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, ResourceProperties.class })
public class ErrorWebFluxAutoConfiguration {
private final ServerProperties serverProperties;
public ErrorWebFluxAutoConfiguration(ServerProperties serverProperties) {
this.serverProperties = serverProperties;
}
//生成全局异常处理类
@Bean
@ConditionalOnMissingBean(value = ErrorWebExceptionHandler.class, search = SearchStrategy.CURRENT)
@Order(-1)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
ResourceProperties resourceProperties, ObjectProvider<ViewResolver> viewResolvers,
ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {
DefaultErrorWebExceptionHandler exceptionHandler = new DefaultErrorWebExceptionHandler(errorAttributes,
resourceProperties, this.serverProperties.getError(), applicationContext);
exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
return exceptionHandler;
}
//生成错误属性Bean
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
return new DefaultErrorAttributes(this.serverProperties.getError().isIncludeException());
}
}
我们可以很清晰的看到,在这个自动装配类中,只有两个简单的Bean ,一个是 DefaultErrorAttributes 这个是用来存储和操作 异常错误信息的。还有一个 DefaultErrorWebExceptionHandler这个就是Spring Boot 原生的处理。
我们首先来看一下 DefaultErrorWebExceptionHandler 这个类。我这里就把跟自定义相关的处理代码列出
//异常处理方法
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
if (exchange.getResponse().isCommitted() || isDisconnectedClientError(throwable)) {
return Mono.error(throwable);
}
//将异常信息存储到 errorAttributes 对象中
this.errorAttributes.storeErrorInformation(throwable, exchange);
ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
return getRoutingFunction(this.errorAttributes).route(request).switchIfEmpty(Mono.error(throwable))
.flatMap((handler) -> handler.handle(request))
.doOnNext((response) -> logError(request, response, throwable))
.flatMap((response) -> write(exchange, response));
}
//webFlux RouterFunction 定义
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
}
//对于 HTTP header头部 accept 属性值为 text/html 做指定的处理
protected RequestPredicate acceptsTextHtml() {
return (serverRequest) -> {
try {
List<MediaType> acceptedMediaTypes = serverRequest.headers().accept();
acceptedMediaTypes.remove(MediaType.ALL);
MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
return acceptedMediaTypes.stream().anyMatch(MediaType.TEXT_HTML::isCompatibleWith);
}
catch (InvalidMediaTypeException ex) {
return false;
}
};
}
这里简单的三个方法
第一个方法就是处理全局异常的核心方法,其实我们可以看到,该方法将 异常 保存到了 errorAttributes对象中。而稍后的方法会将 errorAttributes中的内容取出然后返回。
第二个方法就是Spring5 的 WebFlux API 定义的方式,其类似于 在 Servlet 中的 DispatcherServlet中的 doDispatch 方法的逻辑注册。这里就不多做赘述,可以查看相关博客。这个路由的定义就是调用第三个方法 如果接受到的请求头中 含有 text/html 则渲染页面,否则 返回JSON格式的内容。 具体方法如下
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)
.body(BodyInserters.fromValue(error));
}
我们可以看到这里操作了 ErrorAttributes 具体的操作是在下面的代码
/**
* Extract the error attributes from the current request, to be used to populate error
* views or JSON payloads.
* @param request the source request
* @param includeStackTrace whether to include the error stacktrace information
* @return the error attributes as a Map.
*/
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
return this.errorAttributes.getErrorAttributes(request, includeStackTrace);
}
到此我们其实就可以看到,其实我们想自定义 全局异常处理的返回,只需要操作 ErrorAttributes这个对象即可,其实不需要去重写那么一大坨代码。
实现
- 新建一个 ExtensionErrorAttributes类 实现ErrorAttributes接口,然后我们将 DefaultErrorAttributes类 原模原样的抄过来,只修改其getErrorAttributes方法即可。
@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap<>();
Throwable error = getError(request);
MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations
.from(error.getClass(), MergedAnnotations.SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
HttpStatus errorStatus = determineHttpStatus(error, responseStatusAnnotation);
//永远返回status 为200
errorAttributes.put("status", 200);
//抛出的异常code
errorAttributes.put("code", errorStatus.value());
//自定义的 异常内容
errorAttributes.put("message", determineMessage(error, responseStatusAnnotation));
handleException(errorAttributes, determineException(error), includeStackTrace);
return errorAttributes;
}
- 实现ErrorWebExceptionHandler方法,模拟DefaultErrorWebExceptionHandler
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class GlobalExceptionConfig implements ErrorWebExceptionHandler {
private static final ObjectMapper MAPPER = new ObjectMapper();
private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;
static {
Set<String> exceptions = new HashSet<>();
exceptions.add("AbortedException");
exceptions.add("ClientAbortException");
exceptions.add("EOFException");
exceptions.add("EofException");
DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions);
}
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
try {
if (exchange.getResponse().isCommitted() || isDisconnectedClientError(throwable)) {
return Mono.error(throwable);
}
ServerHttpResponse response = exchange.getResponse();
ResponseData<Object> responseData;
if (throwable instanceof ResponseStatusException) {
ResponseStatusException rse = (ResponseStatusException) throwable;
默认抛出是该异常ResponseStatusException,可以设置exchange的attribute来进一步判断
转化成一个结构返回体 responseData
} else {
转化成一个结构返回体 responseData
}
DataBuffer dataBuffer = response.bufferFactory()
.allocateBuffer().write(MAPPER.writeValueAsString(responseData).getBytes());
response.getHeaders().setContentType(MediaType.APPLICATION_JSON_UTF8);
return response.writeAndFlushWith(Mono.just(ByteBufMono.just(dataBuffer)));
} catch (JsonProcessingException e) {
log.error("json处理异常:", e);
return Mono.error(throwable);
}
}
private boolean isDisconnectedClientError(Throwable ex) {
return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName())
|| isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
}
private boolean isDisconnectedClientErrorMessage(String message) {
message = (message != null) ? message.toLowerCase() : "";
return (message.contains("broken pipe") || message.contains("connection reset by peer"));
}
}
网友评论