欢送拜访我的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注册了两个实例,<font color="red">每个都十分重要</font>,咱们先关注第一个,也就是说ErrorWebExceptionHandler的实现类是DefaultErrorWebExceptionHandler:
- 解决异样时,会通过FluxOnErrorResume调用到这个ErrorWebExceptionHandler的handle办法解决,该办法在其父类AbstractErrorWebExceptionHandler.java中,如下图,红框地位的代码是要害,异样返回内容就是在这里决定的:
- 开展这个getRoutingFunction办法,可见会调用renderErrorResponse来解决响应:
@Overrideprotected 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); }
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字段
- 小小拦路虎,见招拆招
- 简略易用,通过注解管制返回信息
- 终极计划,齐全定制返回内容
你不孤独,欣宸原创一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 数据库+中间件系列
- DevOps系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界...
https://github.com/zq2599/blog_demos