聊聊Spring Cloud Gateway过滤器精确控制异常返回问题

 更新时间:2021年11月25日 08:52:48   作者:程序员欣宸  
这篇文章主要介绍了Spring Cloud Gateway过滤器精确控制异常返回问题,本篇任务就是分析上述现象的原因,通过阅读源码搞清楚返回码和响应body生成的具体逻辑,需要的朋友可以参考下

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览在《Spring Cloud Gateway修改请求和响应body的内容》一文中,咱们通过filter成功修改请求body的内容,当时留下个问题:在filter中如果发生异常(例如请求参数不合法),抛出异常信息的时候,调用方收到的返回码和body都是Spring Cloud Gateway框架处理后的,调用方无法根据这些内容知道真正的错误原因,如下图:

本篇任务就是分析上述现象的原因,通过阅读源码搞清楚返回码和响应body生成的具体逻辑

  • 这里将分析结果提前小结出来,如果您很忙碌没太多时间却又想知道最终原因,直接关注以下小结即可:
  1. Spring Cloud Gateway应用中,有个ErrorAttributes类型的bean,它的getErrorAttributes方法返回了一个map
  2. 应用抛出异常时,返回码来自上述map的status的值,返回body是整个map序列化的结果
  3. 默认情况下ErrorAttributes的实现类是DefaultErrorAttributes
  • 再看上述map的status值(也就是response的返回码),在DefaultErrorAttributes是如何生成的:

先看异常对象是不是ResponseStatusException类型

  1. 如果是ResponseStatusException类型,就调用异常对象的getStatus方法作为返回值
  2. 如果不是ResponseStatusException类型,再看异常类有没有ResponseStatus注解,
  3. 如果有,就取注解的code属性作为返回值
  4. 如果异常对象既不是ResponseStatusException类型,也没有ResponseStatus注解,就返回500

最后看map的message字段(也就是response body的message字段),在DefaultErrorAttributes是如何生成的:

  1. 异常对象是不是BindingResult类型
  2. 如果不是BindingResult类型,就看是不是ResponseStatusException类型
  3. 如果是,就用getReason作为返回值
  4. 如果也不是ResponseStatusException类型,就看异常类有没有ResponseStatus注解,如果有就取该注解的reason属性作为返回值
  5. 如果通过注解取得的reason也无效,就返回异常的getMessage字段

上述内容就是本篇精华,但是并未包含分析过程,如果您对Spring Cloud源码感兴趣,请允许欣宸陪伴您来一次短暂的源码阅读之旅

Spring Cloud Gateway错误处理源码

首先要看的是配置类ErrorWebFluxAutoConfiguration.java,这里面向spring注册了两个实例,每个都非常重要,咱们先关注第一个,也就是说ErrorWebExceptionHandler的实现类是DefaultErrorWebExceptionHandler:

处理异常时,会通过FluxOnErrorResume调用到这个ErrorWebExceptionHandler的handle方法处理,该方法在其父类AbstractErrorWebExceptionHandler.java中,如下图,红框位置的代码是关键,异常返回内容就是在这里决定的:

展开这个getRoutingFunction方法,可见会调用renderErrorResponse来处理响应:

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

打开renderErrorResponse方法,如下所示,真相大白了!

protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
  // 取出所有错误信息
  Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
  
  // 构造返回的所有信息 
  return ServerResponse
           // 控制返回码
           .status(getHttpStatus(error))
           // 控制返回ContentType
           .contentType(MediaType.APPLICATION_JSON)
           // 控制返回内容
           .body(BodyInserters.fromValue(error));
}

通过上述代码,咱们得到两个重要结论:

  • 返回给调用方的状态码,取决于getHttpStatus方法的返回值
  • 返回给调用方的body,取决于error的内容

都已经读到了这里,自然要看看getHttpStatus的内部,如下所示,status来自入参:

protected int getHttpStatus(Map<String, Object> errorAttributes) {
  return (int) errorAttributes.get("status");
}
  • 至此,咱们可以得出一个结论:getErrorAttributes方法的返回值是决定返回码和返回body的关键!
  • 来看看这个getErrorAttributes方法的庐山真面吧,在DefaultErrorAttributes.java中(回忆刚才看ErrorWebFluxAutoConfiguration.java的时候,前面曾提到里面的东西都很重要,也包括errorAttributes方法):
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Map<String, Object> errorAttributes = this.getErrorAttributes(request, options.isIncluded(Include.STACK_TRACE));
        if (Boolean.TRUE.equals(this.includeException)) {
            options = options.including(new Include[]{Include.EXCEPTION});
        }

        if (!options.isIncluded(Include.EXCEPTION)) {
            errorAttributes.remove("exception");
        }

        if (!options.isIncluded(Include.STACK_TRACE)) {
            errorAttributes.remove("trace");
        }

        if (!options.isIncluded(Include.MESSAGE) && errorAttributes.get("message") != null) {
            errorAttributes.put("message", "");
        }

        if (!options.isIncluded(Include.BINDING_ERRORS)) {
            errorAttributes.remove("errors");
        }

        return errorAttributes;
    }

篇幅所限,就不再展开上述代码了,直接上结果吧:

  • 返回码来自determineHttpStatus的返回
  • message字段来自determineMessage的返回打开determineHttpStatus方法,终极答案揭晓,请关注中文注释:
private HttpStatus determineHttpStatus(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        // 异常对象是不是ResponseStatusException类型
        return error instanceof ResponseStatusException 
        // 如果是ResponseStatusException类型,就调用异常对象的getStatus方法作为返回值
        ? ((ResponseStatusException)error).getStatus() 
        // 如果不是ResponseStatusException类型,再看异常类有没有ResponseStatus注解,
        // 如果有,就取注解的code属性作为返回值
        : (HttpStatus)responseStatusAnnotation.getValue("code", HttpStatus.class)
        // 如果异常对象既不是ResponseStatusException类型,也没有ResponseStatus注解,就返回500
        .orElse(HttpStatus.INTERNAL_SERVER_ERROR);
    }

另外,message字段的内容也确定了:

 private String determineMessage(Throwable error, MergedAnnotation<ResponseStatus> responseStatusAnnotation) {
        // 异常对象是不是BindingResult类型
        if (error instanceof BindingResult) {
            // 如果是,就用getMessage作为返回值
            return error.getMessage();
        } 
        // 如果不是BindingResult类型,就看是不是ResponseStatusException类型
        else if (error instanceof ResponseStatusException) {
            // 如果是,就用getReason作为返回值
            return ((ResponseStatusException)error).getReason();
        } else {
            // 如果也不是ResponseStatusException类型,
            // 就看异常类有没有ResponseStatus注解,如果有就取该注解的reason属性作为返回值
            String reason = (String)responseStatusAnnotation.getValue("reason", String.class).orElse("");
            if (StringUtils.hasText(reason)) {
                return reason;
            } else {
                // 如果通过注解取得的reason也无效,就返回异常的getMessage字段
                return error.getMessage() != null ? error.getMessage() : "";
            }
        }
    }
  • 至此,源码分析已完成,最终的返回码和返回内容究竟如何控制,相信聪明的您心里应该有数了,下一篇《实战篇》咱们趁热打铁,写代码试试精确控制返回码和返回内容
  • 提前剧透,接下来的《实战篇》会有以下内容呈现:
  • 直接了当,控制返回码和body中的error字段
  • 小小拦路虎,见招拆招
  • 简单易用,通过注解控制返回信息
  • 终极方案,完全定制返回内容

到此这篇关于Spring Cloud Gateway过滤器精确控制异常返回(分析篇)的文章就介绍到这了,更多相关Spring Cloud Gateway过滤器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • 使用Get方式提交数据到Tomcat服务器的方法

    使用Get方式提交数据到Tomcat服务器的方法

    这篇文章将介绍向服务器发送数据,并且服务器将数据的处理结果返回给客户端,本文给大家介绍使用Get方式向服务器发送数据,感兴趣的朋友一起学习吧
    2016-04-04
  • Java利用Selenium操作浏览器的示例详解

    Java利用Selenium操作浏览器的示例详解

    本文主要介绍如何使用java代码利用Selenium操作浏览器,某些网页元素加载慢,如何操作元素就会把找不到元素的异常,此时需要设置元素等待,等待元素加载完,再操作,感兴趣的可以了解一下
    2023-01-01
  • 如何把VS Code打造成Java开发IDE

    如何把VS Code打造成Java开发IDE

    这篇文章主要介绍了如何把VS Code打造成Java开发IDE,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-10-10
  • Java面试题冲刺第八天--Spring框架2

    Java面试题冲刺第八天--Spring框架2

    这篇文章主要为大家分享了最有价值的三道Spring框架面试题,涵盖内容全面,包括数据结构和算法相关的题目、经典面试编程题等,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • java实现遍历Map的方法

    java实现遍历Map的方法

    这篇文章主要介绍了java实现遍历Map的方法,以简单实例形式分析了java针对HashMap的遍历技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-09-09
  • 解决SpringBoot中使用@Async注解失效的问题

    解决SpringBoot中使用@Async注解失效的问题

    这篇文章主要介绍了解决SpringBoot中使用@Async注解失效的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • java异步编程CompletableFuture使用示例详解

    java异步编程CompletableFuture使用示例详解

    这篇文章主要为大家介绍了java异步编程CompletableFuture使用示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-11-11
  • Spring Boot集成MinIO对象存储服务器操作步骤

    Spring Boot集成MinIO对象存储服务器操作步骤

    通过Spring Boot集成MinIO,你可以在应用中方便地进行文件的存储和管理,本文给大家分享Spring Boot集成MinIO对象存储服务器详细操作步骤,感兴趣的朋友一起看看吧
    2024-01-01
  • 记一次Feign中实现传实体Bean的问题

    记一次Feign中实现传实体Bean的问题

    这篇文章主要介绍了记一次Feign中如何传实体Bean的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Spring中容器创建的四种方式示例

    Spring中容器创建的四种方式示例

    这篇文章主要介绍了Spring中容器创建的四种方式示例,Spring容器是Spring框架的核心部分,它负责管理和组织应用程序中的对象,它提供了一种轻量级的、非侵入式的方式来实现对象的创建、依赖注入和生命周期管理,需要的朋友可以参考下
    2023-10-10

最新评论