聊聊Spring Cloud Gateway过滤器精确控制异常返回问题
作者:程序员欣宸 发布时间:2022-06-23 01:04:14
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览在《Spring Cloud Gateway修改请求和响应body的内容》一文中,咱们通过filter成功修改请求body的内容,当时留下个问题:在filter中如果发生异常(例如请求参数不合法),抛出异常信息的时候,调用方收到的返回码和body都是Spring Cloud Gateway框架处理后的,调用方无法根据这些内容知道真正的错误原因,如下图:
本篇任务就是分析上述现象的原因,通过阅读源码搞清楚返回码和响应body生成的具体逻辑
这里将分析结果提前小结出来,如果您很忙碌没太多时间却又想知道最终原因,直接关注以下小结即可:
Spring Cloud Gateway应用中,有个ErrorAttributes类型的bean,它的getErrorAttributes方法返回了一个map
应用抛出异常时,返回码来自上述map的status的值,返回body是整个map序列化的结果
默认情况下ErrorAttributes的实现类是DefaultErrorAttributes
再看上述map的status值(也就是response的返回码),在DefaultErrorAttributes是如何生成的:
先看异常对象是不是ResponseStatusException类型
如果是ResponseStatusException类型,就调用异常对象的getStatus方法作为返回值
如果不是ResponseStatusException类型,再看异常类有没有ResponseStatus注解,
如果有,就取注解的code属性作为返回值
如果异常对象既不是ResponseStatusException类型,也没有ResponseStatus注解,就返回500
最后看map的message字段(也就是response body的message字段),在DefaultErrorAttributes是如何生成的:
异常对象是不是BindingResult类型
如果不是BindingResult类型,就看是不是ResponseStatusException类型
如果是,就用getReason作为返回值
如果也不是ResponseStatusException类型,就看异常类有没有ResponseStatus注解,如果有就取该注解的reason属性作为返回值
如果通过注解取得的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字段
小小拦路虎,见招拆招
简单易用,通过注解控制返回信息
终极方案,完全定制返回内容
来源:https://www.cnblogs.com/bolingcavalry/p/15601149.html


猜你喜欢
- 目录前提第一步、去官网创建高德Key第二步 通过Gradle集成SDK(方便):第三步 配置
- 平时项目中只要涉及表,那么一定能接触到众多各式各样的ID编号,博主整理一些常用的ID格式,整合一个ID生成工具类,供大家参考,如果有什么不足
- //构造文件File类File f=new File(fileName);//判断是否为目录f.isDirectory();//获取目录下的
- 什么是NPOI?NPOI是指构建在POI 3.x版本之上的一个程序,NPOI可以在没有安装Office的情况下对Word或Excel文档进行
- 方式一:通过java.net.InetAddress类获取public void test1() { try { InetAdd
- 目录前言HuTool 中的一些常用工具类日期相关 API随机工具图片工具彩色转换成黑白添加文字水印加密解密工具布隆过滤器邮件工具HTML 工
- 在Android中因为不同像素手机的多样化,对于一张图片,放大不同的手机上因像素不同显示上也会有区别。现有如下需求:将一张图片宽度充满整个屏
- 本文实例讲述了java在网页上面抓取邮件地址的方法。分享给大家供大家参考。具体实现方法如下:import java.io.BufferedR
- @ConfigurationProperties源码分析@ConfigurationProperties主要作用就是将prefix属性指定的
- 简介官方API文档Scaffold的of方法说明有说明调用Scaffold.of方法是在Scallfold的子组件的Build方法中,也就是
- 1 起因在实际业务开发中, 我们经常会遇到需要临时创建一个数组的情况, 今天我们就来讲一下Java中ArrayList初始化的方法2 解决方
- 前言Java作为一种平台无关性的语言,其主要依靠于Java虚拟机——JVM,我们写好的代码会被编译成class文件,再由JVM进行加载、解析
- 前言:线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、
- 最近项目中需要根据模板生成word文档,模板文件也是word文档。当时思考一下想用POI API来做,但是觉得用起来相对复杂。后来又找了一种
- 前言我们通常使用Spring boot做项目搭建的基础框架,必然少不了它的内置日志框架Logback,在spring-boot-starte
- 现在已经进入了2018年,Android 8.0系统也逐渐开始普及起来了。三星今年推出的最新旗舰机Galaxy S9已经搭载了Android
- using 指令有两个用途: 允许在命名空间中使用类型,以便您不必限定在该命名空间中使用的类型。 为命名空间创建别名。 using
- 一、简介线程安全概念:线程安全是指在当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出
- 再看文章之前,希望大家先打开自己的微信点到朋友圈中去,仔细观察是不是发现朋友圈里的有个“九宫格”的图片区域,点击图片又会跳到图片的详细查看页
- SpringBoot访问html和js等静态资源配置把静态资源放到resources/static下,这是springboot静态资源默认访