我的编程空间,编程开发者的网络收藏夹
学习永远不晚

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

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

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

本文小编为大家详细介绍“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方法的代码我们看出主要是实现了以下的事项:

    • 判断response是否已经提交,并且调用isDisconnectedClientError方法判断是否是客户端断开连接。

    • 如果已经断开连接,已经无法将消息发送给客户端,直接Mono.error(throwable)抛出异常。

    • 将异常信息存储到exchange中。

    • 创建request。

    • 获取路由函数。

    • 调用路由函数的handle方法。

    • 然后执行日志记录。

    • 最后将response写入到exchange中。

    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);        }    }

    上述代码的含义:

    • 如果是debug级别的日志,就以debug的方式打印异常信息。

    • 如果response的状态码是500,就打印异常信息。

    用到的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();        }    }

    上述代码的含义是:

    • 如果viewName对应的模板存在,就使用模板渲染错误页面。

    • 否则就使用静态资源渲染错误页面。 在使用资源渲染的时候,调用resolveResource方法,代码如下:

    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();    }

    其中

    • this.getData(errorStatus)通过错误的code获得错误页面的名称。

    • this.renderErrorView(viewName, responseBody, error),通过错误页面的名称,错误信息,错误状态码,获得错误页面的响应体。

    • this.renderDefaultErrorView(responseBody, error),通过错误信息,错误状态码,获得默认的错误页面的响应体。

    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));    }

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

    • 通过getErrorAttributes方法获得错误信息

    • 获得错误状态码

    • 设置为json格式的响应体

    • 返回错误信息

    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;    }

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

    • 获得ErrorAttributeOptions对象

    • 判断是否包含异常信息

    • 判断是否包含堆栈信息

    • 判断是否包含错误信息

    • 判断是否包含绑定错误信息 如果包含这些信息,就将对应的信息加入到ErrorAttributeOptions对象中。而这些判断主要是通过ErrorProperties对象中的配置来判断的。

    自定义自己的异常处理类

    当认为默认的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怎么实现”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

    免责声明:

    ① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

    ② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

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

    下载Word文档到电脑,方便收藏和打印~

    下载Word文档

    猜你喜欢

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

    本文小编为大家详细介绍“Spring Cloud gateway自定义错误处理Handler怎么实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Spring Cloud gateway自定义错误处理Handler怎么实现”文章能帮助大家
    2023-07-05

    一文吃透Spring Cloud gateway自定义错误处理Handler

    这篇文章主要为大家介绍了一文吃透Spring Cloud gateway自定义错误处理Handler方法,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2023-03-01

    Spring Cloud Gateway自定义异常处理Exception Handler的示例分析

    这篇文章给大家分享的是有关Spring Cloud Gateway自定义异常处理Exception Handler的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。方法1: ErrorWebExceptio
    2023-06-20

    Spring Cloud OAuth2怎么实现自定义token返回格式

    这篇“Spring Cloud OAuth2怎么实现自定义token返回格式”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“S
    2023-07-02

    shiro与spring security怎么用自定义异常处理401错误

    这篇文章主要介绍shiro与spring security怎么用自定义异常处理401错误,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!shiro与spring security自定义异常处理401背景现在是前后端分离
    2023-06-21

    基于spring-security出现401 403错误自定义处理的示例分析

    这篇文章将为大家详细讲解有关基于spring-security出现401 403错误自定义处理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。spring-security 401 403错误自定
    2023-06-20

    在spring中利用security怎么实现自定义决策管理器

    在spring中利用security怎么实现自定义决策管理器?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。首先介绍下Spring的决策管理器,其接口为AccessDecis
    2023-05-31

    SpringBoot怎么整合RabbitMq自定义消息监听容器来实现消息批量处理

    这篇文章主要介绍“SpringBoot怎么整合RabbitMq自定义消息监听容器来实现消息批量处理”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“SpringBoot怎么整合RabbitMq自定义消息
    2023-07-05

    编程热搜

    • Python 学习之路 - Python
      一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
      Python 学习之路 - Python
    • chatgpt的中文全称是什么
      chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
      chatgpt的中文全称是什么
    • C/C++中extern函数使用详解
    • C/C++可变参数的使用
      可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
      C/C++可变参数的使用
    • css样式文件该放在哪里
    • php中数组下标必须是连续的吗
    • Python 3 教程
      Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
      Python 3 教程
    • Python pip包管理
      一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
      Python pip包管理
    • ubuntu如何重新编译内核
    • 改善Java代码之慎用java动态编译

    目录