这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos
Spring Cloud Gateway应用中,处理请求时若发生异常未被捕获,请求方收到的响应是系统默认的内容,无法满足实际业务需求
因此,从前一篇文章[《Spring Cloud Gateway过滤器精确控制异常返回(分析篇)》]开始,咱们深入分析了Spring Cloud Gateway的相关源码,了解到全局异常的处理细节,然后,通过前文[《Spring Cloud Gateway过滤器精确控制异常返回(实战,控制http返回码和message字段)》]的实战,咱们已经能随意设置http返回码,以及body中的message字段,也就是控制下图两个红框中的内容:
{ # 这是有具体业务含义的返回码 "code": "010020003", # 这是能精确描述错误原因的文本信息 "message": "请确保请求参数中的user-id字段是有效的", # 这是常规的业务数据,发生异常时该字段为空 "data": null }
名称 | 链接 | 备注 |
---|---|---|
项目主页 | https://github.com/zq2599/blog_demos | 该项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
在动手前做好充足的理论分析,写出的代码才能正常工作
打开DefaultErrorWebExceptionHandler.java,找到renderErrorResponse方法,来看看Spring Cloud Gateway原本是如何构造异常返回内容的:
新增一个异常类CustomizeInfoException.java,该类有三个字段:http返回码、业务返回码、业务描述信息
在返回异常的代码位置,使用CustomizeInfoException类来抛出异常,按照实际业务场景设置CustomizeInfoException实例的各个字段
新增MyErrorWebExceptionHandler.java,继承自DefaultErrorWebExceptionHandler,重写了renderErrorResponse方法,这里面检查异常实例是否是CustomizeInfoException类型,如果是,就从其中取出http返回码、业务返回码、业务描述信息等字段,构造返回body的内容,异常实例若不是CustomizeInfoException类型,就保持之前的处理逻辑不变;
新增configuration类,用于将MyErrorWebExceptionHandler实例注册到spring环境
package com.bolingcavalry.changebody.exception; import lombok.Data; import org.springframework.http.HttpStatus; @Data public class CustomizeInfoException extends Exception { /** * http返回码 */ private HttpStatus httpStatus; /** * body中的code字段(业务返回码) */ private String code; /** * body中的message字段(业务返回信息) */ private String message; }
@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)); } }
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)); } }
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请求,地址是http://localhost:8081/hello/change,如下图,红框2中的http返回码是咱们代码里设置的,红框3显示返回的内容就是咱们定制的那三个字段:
我是欣宸,期待与您一同畅游Java世界…
https://github.com/zq2599/blog_demos