博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
SpringCloud Alibaba微服务实战二十四 - SpringCloud Gateway的全局异常处理
阅读量:625 次
发布时间:2019-03-14

本文共 5429 字,大约阅读时间需要 18 分钟。

设为星标,每天进步一点点!

前言

在单体SpringBoot项目中我们需要捕获全局异常只需要在项目中配置 @RestControllerAdvice@ExceptionHandler就可以针对不同类型异常进行统一处理,统一包装后返回给前端调用方。

@Slf4j@RestControllerAdvicepublic class RestExceptionHandler {    /**     * 默认全局异常处理。     * @return ResultData     */    @ExceptionHandler(Exception.class)    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)    public ResultData
 exception(Exception e) {        log.error("全局异常信息 ex={}", e.getMessage(), e);        return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());    }}

但是在微服务架构下,例如网关调用业务系统失败(比如网关层jwt token解析异常、服务下线)这时候应用层的 @RestControllerAdvice就会不生效,因为此时流量根本没到应用层。

下面我们分别模拟两种场景,让大家感受一下:

  • jwt解析异常

jwt解析异常

故意写错token让其无法解析,后端返回的数据为:

{  "timestamp": "2020-12-22T02:32:03.143+0000",  "path": "/account-service/account/test/jianzh5",  "status": 500,  "error": "Internal Server Error",  "message": "Cannot convert access token to JSON",  "requestId": "7043b1f8-1"}
  • 服务下线

服务下线异常

停止后端服务,后端返回的数据为:

{  "timestamp": "2020-12-22T02:36:13.281+0000",  "path": "/account-service/account/getByCode/jianzh5",  "status": 503,  "error": "Service Unavailable",  "message": "Unable to find instance for account-service",  "requestId": "7043b1f8-6"}

在前后端分离的项目中,一般都要约定项目整体返回格式,前端需要根据返回数据确定页面逻辑。在我们项目例子中我们约定好的响应格式如下:

@Data@ApiModel(value = "统一返回结果封装",description = "接口返回统一结果")public class ResultData
 {    /** 结果状态 ,具体状态码参见ResultData.java*/    @ApiModelProperty(value = "状态码")    private int status;    @ApiModelProperty(value = "响应信息")    private String message;    @ApiModelProperty(value = "后端返回结果")    private T data;    @ApiModelProperty(value = "后端响应状态")    private boolean success;    @ApiModelProperty(value = "响应时间戳")    private long timestamp ;    public ResultData (){        this.timestamp = System.currentTimeMillis();    } ...}

很显然在这些情况下返回的异常数据并不符合我们的预期格式,我们需要改造网关返回数据。

原因剖析

在SpringCloud gateway中默认使用 DefaultErrorWebExceptionHandler来处理异常。这个可以通过配置类 ErrorWebFluxAutoConfiguration得之。

DefaultErrorWebExceptionHandler类中的默认异常处理逻辑如下:

public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler { ...    protected RouterFunction
 getRoutingFunction(ErrorAttributes errorAttributes) {        return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);    }   ...}

根据请求头确认返回什么资源格式。

返回的数据内容在 DefaultErrorAttributes类中构建而成。

public class DefaultErrorAttributes implements ErrorAttributes { ...    public Map
 getErrorAttributes(ServerRequest request, boolean includeStackTrace) {        Map
 errorAttributes = new LinkedHashMap();        errorAttributes.put("timestamp", new Date());        errorAttributes.put("path", request.path());        Throwable error = this.getError(request);        MergedAnnotation
 responseStatusAnnotation = MergedAnnotations.from(error.getClass(), SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);        HttpStatus errorStatus = this.determineHttpStatus(error, responseStatusAnnotation);        errorAttributes.put("status", errorStatus.value());        errorAttributes.put("error", errorStatus.getReasonPhrase());        errorAttributes.put("message", this.determineMessage(error, responseStatusAnnotation));        errorAttributes.put("requestId", request.exchange().getRequest().getId());        this.handleException(errorAttributes, this.determineException(error), includeStackTrace);        return errorAttributes;    } ...}

阅读到这里就可以看到为什么上面会返回那样的数据格式,接下来我们需要改写返回格式。

解决方案

这里我们我们可以自定义一个 CustomErrorWebExceptionHandler类用来继承 DefaultErrorWebExceptionHandler,然后修改生成前端响应数据的逻辑。再然后定义一个配置类,写法可以参考 ErrorWebFluxAutoConfiguration,简单将异常类替换成 CustomErrorWebExceptionHandler类即可。

这种方法大家请自行研究,基本都是复制代码,改写不复杂,这种方法我们就不演示了,这里给大家介绍另外一种写法:

我们定义一个全局异常类 GlobalErrorWebExceptionHandler让其直接实现顶级接口 ErrorWebExceptionHandler重写 handler()方法,在 handler()方法中返回我们自定义的响应类。但是需要注意重写的实现类优先级一定要小于内置 ResponseStatusExceptionHandler 经过它处理的获取对应错误类的响应码。

代码如下:

/** * 网关全局异常处理 * @author javadaily */@Slf4j@Order(-1)@Configuration@RequiredArgsConstructor(onConstructor = @__(@Autowired))public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {    private final ObjectMapper objectMapper;    @Override    public Mono
 handle(ServerWebExchange exchange, Throwable ex) {        ServerHttpResponse response = exchange.getResponse();        if (response.isCommitted()) {            return Mono.error(ex);        }        // 设置返回JSON        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);        if (ex instanceof ResponseStatusException) {            response.setStatusCode(((ResponseStatusException) ex).getStatus());        }        return response.writeWith(Mono.fromSupplier(() -> {            DataBufferFactory bufferFactory = response.bufferFactory();            try {                //返回响应结果                return bufferFactory.wrap(objectMapper.writeValueAsBytes(ResultData.fail(500,ex.getMessage())));            }            catch (JsonProcessingException e) {                log.error("Error writing response", ex);                return bufferFactory.wrap(new byte[0]);            }        }));    }}

测试结果

测试结果

符合我们的预期结果,实现网关层的异常拦截!

以上,希望对你有所帮助。


这里为大家准备了一份小小的礼物,关注公众号,输入如下代码,即可获得百度网盘地址,无套路领取!

001:《程序员必读书籍》
002:《从无到有搭建中小型互联网公司后台服务架构与运维架构》
003:《互联网企业高并发解决方案》
004:《互联网架构教学视频》
006:《SpringBoot实现点餐系统》
007:《SpringSecurity实战视频》
008:《Hadoop实战教学视频》
009:《腾讯2019Techo开发者大会PPT》

010: 微信交流群

 

 

转载地址:http://tucoz.baihongyu.com/

你可能感兴趣的文章