Spring Cloud Gateway 全局通用异常处理

为什么需要全局异常处理

在传统 Spring Boot 应用中, 我们 @ControllerAdvice 来处理全局的异常,进行统一包装返回

1// 摘至 spring cloud alibaba console 模块处理 2@ControllerAdvice 3public class ConsoleExceptionHandler { 4 5 @ExceptionHandler(AccessException.class) 6 private ResponseEntity<String> handleAccessException(AccessException e) { 7 return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg()); 8 } 9}

例如: ③ 处应用调用数据库异常,通过 @ControllerAdvice 包装异常请求响应给客户端

但在微服务架构下, 例如 ② 处 网关调用业务微服务失败(转发失败、调用异常、转发失败),在应用设置的 @ControllerAdvice 将失效,因为流量根本没有转发到应用上处理。

如上图: 模拟所有路由断言都不匹配 404 , 和 spring boot 默认保持一致的错误输出页面。 显然我们在网关同样配置 @ControllerAdvice 是不能解决问题,因为 spring cloud gateway 是基于 webflux 反应式编程。

解决方法

默认处理流程

  • ExceptionHandlingWebHandler 作为 spring cloud gateway 最核心 WebHandler 的一部分会进行异常处理的过滤

    public class ExceptionHandlingWebHandler extends WebHandlerDecorator { @Override public Mono<Void> handle(ServerWebExchange exchange) { Mono<Void> completion; try { completion = super.handle(exchange); } catch (Throwable ex) { completion = Mono.error(ex); }

    1 // 获取全局的 WebExceptionHandler 执行 2 for (WebExceptionHandler handler : this.exceptionHandlers) { 3 completion = completion.onErrorResume(ex -> handler.handle(exchange, ex)); 4 } 5 return completion; 6}

    }

  • 默认实现 DefaultErrorWebExceptionHandler

1public class DefaultErrorWebExceptionHandler { 2 3 @Override 4 protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) { 5 // 根据客户端 `accpet` 请求头决定返回什么资源,如上浏览器返回的是 页面 6 return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse); 7 } 8} 9 10 11 12// 模拟指定 `accpet` 情况 13curl --location --request GET 'http://localhost:9999/adminx/xx' 18:09:23 14 --header 'Accept: application/json' 15{"timestamp":"2020-05-24 18:09:24","path":"/adminx/xx","status":404,"error":"Not Found","message":null,"requestId":"083c48e3-2"}

重写 ErrorWebExceptionHandler

1/** 2 * @author lengleng 3 * @date 2020/5/23 4 * <p> 5 * 网关异常通用处理器,只作用在webflux 环境下 , 优先级低于 {@link ResponseStatusExceptionHandler} 执行 6 */ 7@Slf4j 8@Order(-1) 9@RequiredArgsConstructor 10public class GlobalExceptionConfiguration implements ErrorWebExceptionHandler { 11 private final ObjectMapper objectMapper; 12 13 @Override 14 public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) { 15 ServerHttpResponse response = exchange.getResponse(); 16 17 if (response.isCommitted()) { 18 return Mono.error(ex); 19 } 20 21 // header set 22 response.getHeaders().setContentType(MediaType.APPLICATION_JSON); 23 if (ex instanceof ResponseStatusException) { 24 response.setStatusCode(((ResponseStatusException) ex).getStatus()); 25 } 26 27 return response 28 .writeWith(Mono.fromSupplier(() -> { 29 DataBufferFactory bufferFactory = response.bufferFactory(); 30 try { 31 return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage()))); 32 } catch (JsonProcessingException e) { 33 log.warn("Error writing response", ex); 34 return bufferFactory.wrap(new byte[0]); 35 } 36 })); 37 } 38}

总结

  • 重写的 DefaultErrorWebExceptionHandler 优先级一定要小于内置 ResponseStatusExceptionHandler 经过它处理的获取对应错误类的 响应码
  • 其他扩展 可以参考 SentinelBlockExceptionHandler sentinel 整合网关的处理,不过整体和默认的异常处理没有什么区别
  • 基础环境说明:Spring Cloud Hoxton.SR4 & Spring Boot 2.3.0
  • 具体实现代码参考:https://gitee.com/log4j/pig

项目推荐: Spring Cloud 、Spring Security OAuth2的RBAC权限管理系统 欢迎关注

点赞
收藏

评论区

加载中...

相关推荐

MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1

文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

手写Java HashMap源码

HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22

Python之time模块的时间戳、时间字符串格式化与转换

Python处理时间和时间戳的内置模块就有time,和datetime两个,本文先说time模块。关于时间戳的几个概念时间戳,根据1970年1月1日00:00:00开始按秒计算的偏移量。时间元组(struct_time),包含9个元素。 time.struct_time(tm_y

Twitter的分布式自增ID算法snowflake (Java版)

概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移