Spring Cloud gateway自定义错误处理Handler怎么实现

发布时间:2023-03-01 16:13:19 作者:iii
来源:亿速云 阅读:106

本文小编为大家详细介绍“Spring Cloud gateway自定义错误处理Handler怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring Cloud gateway自定义错误处理Handler怎么实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

正文

我们来学习和了解下GatewayExceptionHandler有助于我们处理spring gateway和webFlux中的异常自定义处理。它继承自ErrorWebExceptionHandler, 类关系图如下:

Spring Cloud gateway自定义错误处理Handler怎么实现

通过类上述关系图,我们可以看到,DefaultErrorWebExceptionHandlerErrorWebExceptionHandler的实现类,如果我们不自定义类异常处理器,系统将自动装配DefaultErrorWebExceptionHandler

我们就来庖丁解牛,一步步地来学习下这一组类和接口。

AbstractErrorWebExceptionHandler

Spring Cloud gateway自定义错误处理Handler怎么实现

AbstractErrorWebExceptionHandler实现了ErrorWebExceptionHandler接口,其中handle方法是其核心方法。我们来看下其实现代码:

public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
        // 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。
        // 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。
        if (!exchange.getResponse().isCommitted() && !this.isDisconnectedClientError(throwable)) {
            // exchange.getAttributes().putIfAbsent(ERROR_INTERNAL_ATTRIBUTE, error);
            this.errorAttributes.storeErrorInformation(throwable, exchange);
            // 创建request 
            ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
            return this.getRoutingFunction(this.errorAttributes).route(request).switchIfEmpty(Mono.error(throwable)).flatMap((handler) -> {
                return handler.handle(request);
            }).doOnNext((response) -> {
                this.logError(request, response, throwable);
            }).flatMap((response) -> {
                return this.write(exchange, response);
            });
        } else {
            return Mono.error(throwable);
        }
    }

通过Handle方法的代码我们看出主要是实现了以下的事项:

isDisconnectedClientError方法

private boolean isDisconnectedClientError(Throwable ex) {
        return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName()) ||this.isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
}

DISCONNECTED_CLIENT_EXCEPTIONS是一个集合,里面存放了一些异常信息,如果是这些异常信息, 就认为是客户端断开连接了。 或者通过isDisconnectedClientErrorMessage方法判断是否是客户端断开连接的异常信息。

下面我们来看看isDisconnectedClientErrorMessage方法的实现。

isDisconnectedClientErrorMessage方法:
private boolean isDisconnectedClientErrorMessage(String message) {
        message = message != null ? message.toLowerCase() : "";
        return message.contains("broken pipe") || message.contains("connection reset by peer");
}

上述代码的含义是:如果异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。

小结

综合起来,isDisconnectedClientError方法的含义是:如果DISCONNECTED_CLIENT_EXCEPTIONS集合中包含异常信息的类名或者异常信息中包含“broken pipe”或者“connection reset by peer”,就认为是客户端断开连接了。

NestedExceptionUtils

而isDisconnectedClientErrorMessage方法的参数message来自于NestedExceptionUtils.getMostSpecificCause(ex).getMessage()。我们进一步跟踪源码,可以看到NestedExceptionUtils.getMostSpecificCause(ex)方法的源码如下:

public static Throwable getMostSpecificCause(Throwable ex) {
    Throwable cause;
    Throwable result = ex;
    while(null != (cause = result.getCause()) &amp;&amp; (result != cause)) {
        result = cause;
    }
    return result;
}

上述代码的含义是:如果ex.getCause()不为空,并且ex.getCause()不等于ex,就将ex.getCause()赋值给result,然后继续执行while循环。直到ex.getCause()为空或者ex.getCause()等于ex,就返回result。

getRoutingFunction

我们看到获得路由调用的是getRoutingFunction方法,而这个方法是一个抽象方法,需要在具体的继承类中实现。

logError

protected void logError(ServerRequest request, ServerResponse response, Throwable throwable) {
        if (logger.isDebugEnabled()) {
            logger.debug(request.exchange().getLogPrefix() + this.formatError(throwable, request));
        }
        if (HttpStatus.resolve(response.rawStatusCode()) != null && response.statusCode().equals(HttpStatus.INTERNAL_SERVER_ERROR)) {
            logger.error(LogMessage.of(() -> {
                return String.format("%s 500 Server Error for %s", request.exchange().getLogPrefix(), this.formatRequest(request));
            }), throwable);
        }
    }

上述代码的含义:

用到的formatError方法就是简单的讲错误格式化,代码不再一一分析。

write

在把内容写入的时候用到了私有方法write,我们看看write方法的代码如下:

private Mono<? extends Void> write(ServerWebExchange exchange, ServerResponse response) {
        exchange.getResponse().getHeaders().setContentType(response.headers().getContentType());
        return response.writeTo(exchange, new AbstractErrorWebExceptionHandler.ResponseContext());
    }

上述代码的含义是:将response的contentType设置到exchange的response中,然后调用response的writeTo方法,将response写入到exchange中。 ResponseContext是一个内部类,主要是携带了外部类中的viewResolvers和messageWriters属性。

其他的方法

afterPropertiesSet
public void afterPropertiesSet() throws Exception {
        if (CollectionUtils.isEmpty(this.messageWriters)) {
            throw new IllegalArgumentException("Property 'messageWriters' is required");
        }
}

afterPropertiesSet主要是通过实现InitializingBean接口,实现afterPropertiesSet方法,判断messageWriters是否为空,如果为空就抛出异常。

renderDefaultErrorView
protected Mono<ServerResponse> renderDefaultErrorView(BodyBuilder responseBody, Map<String, Object> error) {
        StringBuilder builder = new StringBuilder();
        Date timestamp = (Date)error.get("timestamp");
        Object message = error.get("message");
        Object trace = error.get("trace");
        Object requestId = error.get("requestId");
        builder.append("<html><body><h3>Whitelabel Error Page</h3>").append("<p>This application has no configured error view, so you are seeing this as a fallback.</p>").append("<div id='created'>").append(timestamp).append("</div>").append("<div>[").append(requestId).append("] There was an unexpected error (type=").append(this.htmlEscape(error.get("error"))).append(", status=").append(this.htmlEscape(error.get("status"))).append(").</div>");
        if (message != null) {
            builder.append("<div>").append(this.htmlEscape(message)).append("</div>");
        }
        if (trace != null) {
            builder.append("<div style='white-space:pre-wrap;'>").append(this.htmlEscape(trace)).append("</div>");
        }
        builder.append("</body></html>");
        return responseBody.bodyValue(builder.toString());
    }

renderDefaultErrorView方法主要是渲染默认的错误页面,如果没有自定义的错误页面,就会使用这个方法渲染默认的错误页面。

renderErrorView
protected Mono<ServerResponse> renderErrorView(String viewName, BodyBuilder responseBody, Map<String, Object> error) {
        if (this.isTemplateAvailable(viewName)) {
            return responseBody.render(viewName, error);
        } else {
            Resource resource = this.resolveResource(viewName);
            return resource != null ? responseBody.body(BodyInserters.fromResource(resource)) : Mono.empty();
        }
    }

上述代码的含义是:

private Resource resolveResource(String viewName) {
        // 获得所有的静态资源的路径
        String[] var2 = this.resources.getStaticLocations();
        int var3 = var2.length;
        // 遍历所有的静态资源
        for(int var4 = 0; var4 < var3; ++var4) {
            String location = var2[var4];
            try {
                // 获得静态资源
                Resource resource = this.applicationContext.getResource(location);
                // 获得静态资源的文件
                resource = resource.createRelative(viewName + ".html");
                if (resource.exists()) {
                    return resource;
                }
            } catch (Exception var7) {
            }
        }
        return null;
    }

DefaultErrorWebExceptionHandler

DefaultErrorWebExceptionHandler是ErrorWebExceptionHandler的默认实现,继承了AbstractErrorWebExceptionHandler。通过对AbstractErrorWebExceptionHandler的分析,我们知道了大致实现原理,下面我们来看看DefaultErrorWebExceptionHandler的具体实现。

getRoutingFunction

在AbstractErrorWebExceptionHandler中,getRoutingFunction方法是一个抽象方法,需要子类实现。DefaultErrorWebExceptionHandler的getRoutingFunction方法的实现如下:

protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
}

我们看到代码中调用了RouterFunctions.route和andRoute方法。RouterFunctions.route的作用是根据请求的accept头信息,判断是否是text/html类型,如果是就调用renderErrorView方法,否则就调用renderErrorResponse方法。

acceptsTextHtml
protected RequestPredicate acceptsTextHtml() {
        return (serverRequest) -> {
            try {
                // 获得请求头中的accept信息
                List<MediaType> acceptedMediaTypes = serverRequest.headers().accept();
                // 定义一个MediaType.ALL的MediaType对象
                MediaType var10001 = MediaType.ALL;
                // 移除MediaType.ALL
                acceptedMediaTypes.removeIf(var10001::equalsTypeAndSubtype);
                // 根据类型和权重对MediaType进行排序
                MediaType.sortBySpecificityAndQuality(acceptedMediaTypes);
                // 获得acceptMediaTypes的stream对象
                Stream var10000 = acceptedMediaTypes.stream();
                // 获得MediaType.TEXT_HTML的MediaType对象
                var10001 = MediaType.TEXT_HTML;
                var10001.getClass();
                // 判断是否有MediaType.TEXT_HTML
                return var10000.anyMatch(var10001::isCompatibleWith);
            } catch (InvalidMediaTypeException var2) {
                return false;
            }
        };
    }

acceptsTextHtml方法的作用是判断请求头中的accept信息是否是text/html类型。

renderErrorView
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
    // 通过getErrorAttributes方法获得错误信息
        Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.TEXT_HTML));
        // 获得错误状态码
        int errorStatus = this.getHttpStatus(error);
        // 获得错误页面的模板名称
        BodyBuilder responseBody = ServerResponse.status(errorStatus).contentType(TEXT_HTML_UTF8);
        // 获得错误页面的模板名称
        return Flux.just(this.getData(errorStatus).toArray(new String[0])).flatMap((viewName) -> {
            return this.renderErrorView(viewName, responseBody, error);
        }).switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled() ? this.renderDefaultErrorView(responseBody, error) : Mono.error(this.getError(request))).next();
    }

其中

renderErrorResponse方法

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
    // 通过getErrorAttributes方法获得错误信息
        Map<String, Object> error = this.getErrorAttributes(request, this.getErrorAttributeOptions(request, MediaType.ALL));
        // 获得错误状态码
        // 设置为json格式的响应体
        // 返回错误信息
        return ServerResponse.status(this.getHttpStatus(error)).contentType(MediaType.APPLICATION_JSON).body(BodyInserters.fromValue(error));
    }

通过上述代码看到主要执行了以下流程:

getErrorAttributeOptions
protected ErrorAttributeOptions getErrorAttributeOptions(ServerRequest request, MediaType mediaType) {
        ErrorAttributeOptions options = ErrorAttributeOptions.defaults();
        if (this.errorProperties.isIncludeException()) {
            options = options.including(new Include[]{Include.EXCEPTION});
        }
        if (this.isIncludeStackTrace(request, mediaType)) {
            options = options.including(new Include[]{Include.STACK_TRACE});
        }
        if (this.isIncludeMessage(request, mediaType)) {
            options = options.including(new Include[]{Include.MESSAGE});
        }
        if (this.isIncludeBindingErrors(request, mediaType)) {
            options = options.including(new Include[]{Include.BINDING_ERRORS});
        }
        return options;
    }

我们看到上述代码执行了下面的流程:

自定义自己的异常处理类

当认为默认的DefaultErrorWebExceptionHandler不满足需求时,我们可以自定义自己的异常处理类。

继承DefaultErrorWebExceptionHandler

只需要继承DefaultErrorWebExceptionHandler类,然后重写getErrorAttributes方法即可。

import org.springframework.context.annotation.Configuration;
@Configuration
@Order(-1)
public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {
    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }
    @Override
    protected Map&lt;String, Object&gt; getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map&lt;String, Object&gt; errorAttributes = super.getErrorAttributes(request, options);
        errorAttributes.put("ext", "自定义异常处理类");
        return errorAttributes;
    }
}

继承AbstractErrorWebExceptionHandler

import org.springframework.context.annotation.Configuration;
@Configuration
@Order(-1)
public class MyErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
    public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = super.getErrorAttributes(request, options);
        errorAttributes.put("ext", "自定义异常处理类");
        return errorAttributes;
    }
}

继承WebExceptionHandler

import org.springframework.context.annotation.Configuration;
@Configuration
@Order(-1)
public class MyErrorWebExceptionHandler implements WebExceptionHandler {
    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        } else {
            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
            response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
            return response.writeWith(Mono.just(response.bufferFactory().wrap("自定义异常处理类".getBytes())));
        }
    }
}

读到这里,这篇“Spring Cloud gateway自定义错误处理Handler怎么实现”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注亿速云行业资讯频道。

推荐阅读:
  1. Springcloud如何实现服务多版本控制
  2. SpringCloud消息总线Bus配置中心实现的示例分析

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

springcloud gateway handler

上一篇:怎么使用Python实现tail

下一篇:MyBatis的SqlSession.getMapper()源码分析

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》