欢送拜访我的GitHub
这里分类和汇总了欣宸的全副原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- Spring Cloud Gateway利用中,解决申请时若产生异样未被捕捉,申请方收到的响应是零碎默认的内容,无奈满足理论业务需要
- 因而,从前一篇文章《Spring Cloud Gateway过滤器准确管制异样返回(剖析篇)》开始,咱们深入分析了Spring Cloud Gateway的相干源码,理解到全局异样的解决细节,而后,通过前文《Spring Cloud Gateway过滤器准确管制异样返回(实战,管制http返回码和message字段)》的实战,咱们曾经能随便设置http返回码,以及body中的message字段,也就是管制下图两个红框中的内容:
- 正如上图所示,异样产生时零碎固定返回8个字段,这就有些不够灵便了,在一些对格局和内容有严格要求的场景下,咱们须要可能齐全管制返回码和返回body的内容,如下所示,只返回三个字段,每个字段都是齐全为业务服务的:
{ # 这是有具体业务含意的返回码 "code": "010020003", # 这是能准确形容谬误起因的文本信息 "message": "请确保申请参数中的user-id字段是无效的", # 这是惯例的业务数据,产生异样时该字段为空 "data": null}
- 明天咱们的指标就是通过编码定制异样产生时的返回信息,具体内容就是上述JSON数据:只有code、message、data三个字段
源码下载
- 本篇实战中的残缺源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blo...):
名称 | 链接 | 备注 |
---|
我的项目主页 | https://github.com/zq2599/blo... | 该我的项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blo... | 该我的项目源码的仓库地址,https协定 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh协定 |
- 这个git我的项目中有多个文件夹,本篇的源码在<font color="blue">spring-cloud-tutorials</font>文件夹下,如下图红框所示:
- <font color="blue">spring-cloud-tutorials</font>文件夹下有多个子工程,本篇的代码是<font color="red">gateway-change-body</font>,如下图红框所示:
为何不必惯例伎俩
- 提到全局异样解决,经验丰富的您应该想到了罕用的ControllerAdvice和ExceptionHandler注解润饰的全局异样解决类,然而Spring Cloud Gateway是基于WebFlux的,咱们之前解决异样时用到的HttpServletRequest在Spring Cloud Gateway中并不实用,因而,不能用ControllerAdvice和ExceptionHandler的伎俩来解决全局异样
基本思路
- 在入手前做好短缺的实践剖析,写出的代码能力失常工作
- 关上DefaultErrorWebExceptionHandler.java,找到renderErrorResponse办法,来看看Spring Cloud Gateway本来是如何结构异样返回内容的:
- 此刻聪慧的您应该想到怎么做了:做个新的类继承DefaultErrorWebExceptionHandler,笼罩其renderErrorResponse办法,新的renderErrorResponse办法中,依照理论业务须要来设置返回内容,没错,这就是咱们的思路,不过还要细化一下,最终具体的步骤如下:
- 新增一个异样类<font color="blue">CustomizeInfoException.java</font>,该类有三个字段:http返回码、业务返回码、业务形容信息
- 在返回异样的代码地位,应用CustomizeInfoException类来抛出异样,依照理论业务场景设置CustomizeInfoException实例的各个字段
- 新增MyErrorWebExceptionHandler.java,继承自DefaultErrorWebExceptionHandler,重写了renderErrorResponse办法,这外面查看异样实例是否是CustomizeInfoException类型,如果是,就从其中取出http返回码、业务返回码、业务形容信息等字段,结构返回body的内容,异样实例若不是CustomizeInfoException类型,就放弃之前的解决逻辑不变;
- 新增configuration类,用于将MyErrorWebExceptionHandler实例注册到spring环境
- 剖析结束,开始编码吧,为了简略起见,本篇不再新增maven子工程,而是基于前文创立的子工程<font color="red">gateway-change-body</font>,在这外面持续写代码;
编码
- 新增异样类<font color="blue">CustomizeInfoException.java</font>:
package com.bolingcavalry.changebody.exception;import lombok.Data;import org.springframework.http.HttpStatus;@Datapublic class CustomizeInfoException extends Exception { /** * http返回码 */ private HttpStatus httpStatus; /** * body中的code字段(业务返回码) */ private String code; /** * body中的message字段(业务返回信息) */ private String message;}
- 批改RequestBodyRewrite.java的apply办法,这外面是在解决申请body,如果查看到没有<font color="blue">user-id</font>字段,就不将申请转发到服务提供方<font color="blue">provider-hello</font>,而是返回谬误,这里的谬误就用CustomizeInfoException类来解决:
@Override public Publisher<String> apply(ServerWebExchange exchange, String body) { try { Map<String, Object> map = objectMapper.readValue(body, Map.class); // 如果申请参数中不含user-id,就返回异样 if (!map.containsKey("user-id")) { CustomizeInfoException customizeInfoException = new CustomizeInfoException(); // 这里返回406,您能够依照业务须要自行调整 customizeInfoException.setHttpStatus(HttpStatus.NOT_ACCEPTABLE); // 这里依照业务须要自行设置code customizeInfoException.setCode("010020003"); // 这里依照业务须要自行设置返回的message customizeInfoException.setMessage("请确保申请参数中的user-id字段是无效的"); return Mono.error(customizeInfoException); } // 获得id int userId = (Integer)map.get("user-id"); // 失去nanme后写入map map.put("user-name", mockUserName(userId)); return Mono.just(objectMapper.writeValueAsString(map)); } catch (Exception ex) { log.error("1. json process fail", ex); return Mono.error(new Exception("1. json process fail", ex)); } }
- 异样解决类MyErrorWebExceptionHandler.java,这里有一处须要<font color="red">重点关注的是:</font>上面的代码仅是参考而已,您无需拘泥于CustomizeInfoException无关的逻辑,齐全能依照业务需要自在设置返回的状态码和body:
package com.bolingcavalry.changebody.handler;import com.bolingcavalry.changebody.exception.CustomizeInfoException;import org.springframework.boot.autoconfigure.web.ErrorProperties;import org.springframework.boot.autoconfigure.web.WebProperties;import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;import org.springframework.boot.web.reactive.error.ErrorAttributes;import org.springframework.context.ApplicationContext;import org.springframework.http.MediaType;import org.springframework.web.reactive.function.BodyInserters;import org.springframework.web.reactive.function.server.ServerRequest;import org.springframework.web.reactive.function.server.ServerResponse;import reactor.core.publisher.Mono;import java.util.HashMap;import java.util.Map;public class MyErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler { public MyErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) { super(errorAttributes, resources, errorProperties, applicationContext); } @Override protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) { // 返回码 int status; // 最终是用responseBodyMap来生成响应body的 Map<String, Object> responseBodyMap = new HashMap<>(); // 这里和父类的做法一样,获得DefaultErrorAttributes整理出来的所有异样信息 Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL)); // 原始的异样信息能够用getError办法获得 Throwable throwable = getError(request); // 如果异样类是咱们定制的,就定制 if (throwable instanceof CustomizeInfoException) { CustomizeInfoException myGatewayException = (CustomizeInfoException) throwable; // http返回码、body的code字段、body的message字段,这三个信息都从CustomizeInfoException实例中获取 status = myGatewayException.getHttpStatus().value(); responseBodyMap.put("code", myGatewayException.getCode()); responseBodyMap.put("message", myGatewayException.getMessage()); responseBodyMap.put("data", null); } else { // 如果不是咱们定制的异样,就维持和父类一样的逻辑 // 返回码 status = getHttpStatus(error); // body内容 responseBodyMap.putAll(error); } return ServerResponse // http返回码 .status(status) // 类型和以前一样 .contentType(MediaType.APPLICATION_JSON) // 响应body的内容 .body(BodyInserters.fromValue(responseBodyMap)); }}
- 最初是配置类MyErrorWebFluxAutoConfiguration.java:
package com.bolingcavalry.changebody.config;import com.bolingcavalry.changebody.handler.MyErrorWebExceptionHandler;import org.springframework.beans.factory.ObjectProvider;import org.springframework.boot.autoconfigure.AutoConfigureBefore;import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;import org.springframework.boot.autoconfigure.web.ServerProperties;import org.springframework.boot.autoconfigure.web.WebProperties;import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;import org.springframework.boot.context.properties.EnableConfigurationProperties;import org.springframework.boot.web.reactive.error.ErrorAttributes;import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.annotation.Order;import org.springframework.http.codec.ServerCodecConfigurer;import org.springframework.web.reactive.config.WebFluxConfigurer;import org.springframework.web.reactive.result.view.ViewResolver;import java.util.stream.Collectors;@Configuration(proxyBeanMethods = false)@AutoConfigureBefore(WebFluxAutoConfiguration.class)public class MyErrorWebFluxAutoConfiguration { private final ServerProperties serverProperties; public MyErrorWebFluxAutoConfiguration(ServerProperties serverProperties) { this.serverProperties = serverProperties; } @Bean @Order(-1) public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes, org.springframework.boot.autoconfigure.web.ResourceProperties resourceProperties, WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers, ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) { MyErrorWebExceptionHandler exceptionHandler = new MyErrorWebExceptionHandler(errorAttributes, resourceProperties.hasBeenCustomized() ? resourceProperties : webProperties.getResources(), this.serverProperties.getError(), applicationContext); exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList())); exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters()); exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders()); return exceptionHandler; }}
验证
- 启动利用gateway-change-body
- 用postman发动POST申请,地址是<font color="blue">http://localhost:8081/hello/c...</font>,如下图,红框2中的http返回码是咱们代码里设置的,红框3显示返回的内容就是咱们定制的那三个字段:
- 至此,管制Spring Cloud Gateway利用异样返回的实战曾经全副实现,从源码剖析联合实战演练,心愿欣宸的文章能陪伴您深刻理解Spring Cloud Gateway,打造出更加弱小的网关利用;
你不孤独,欣宸原创一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 数据库+中间件系列
- DevOps系列
欢送关注公众号:程序员欣宸
微信搜寻「程序员欣宸」,我是欣宸,期待与您一起畅游Java世界...
https://github.com/zq2599/blog_demos