美文网首页spring cloud gateway
springcloud gateway全局异常处理

springcloud gateway全局异常处理

作者: virtual灬zzZ | 来源:发表于2022-11-28 00:02 被阅读0次

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这个对象即可,其实不需要去重写那么一大坨代码。

实现

  1. 新建一个 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;
    }
 
  1. 实现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"));
    }
}

参考:
优雅的自定义Spring Cloud Gateway全局异常处理

Spring Cloud Gateway自定义异常处理Exception Handler的方法小结

相关文章

网友评论

    本文标题:springcloud gateway全局异常处理

    本文链接:https://www.haomeiwen.com/subject/fdjkfdtx.html