SpringCloud Alibaba微服务实战二十四 - SpringCloud Gateway的全局异常处理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: SpringCloud Alibaba微服务实战二十四 - SpringCloud Gateway的全局异常处理

前言


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

@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
    /**
     * 默认全局异常处理。
     * @return ResultData
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultData<String> 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<T> {
    /** 结果状态 ,具体状态码参见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<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
    }
   ...
}

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

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

public class DefaultErrorAttributes implements ErrorAttributes {
 ...
    public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = this.getError(request);
        MergedAnnotation<ResponseStatus> 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<Void> 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]);
            }
        }));
    }
}


测试结果

测试结果

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

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

目录
相关文章
|
1天前
|
JSON Java API
利用Spring Cloud Gateway Predicate优化微服务路由策略
Spring Cloud Gateway 的路由配置中,`predicates`​(断言)用于定义哪些请求应该匹配特定的路由规则。 断言是Gateway在进行路由时,根据具体的请求信息如请求路径、请求方法、请求参数等进行匹配的规则。当一个请求的信息符合断言设置的条件时,Gateway就会将该请求路由到对应的服务上。
93 69
利用Spring Cloud Gateway Predicate优化微服务路由策略
|
20天前
|
JavaScript Java Kotlin
深入 Spring Cloud Gateway 过滤器
Spring Cloud Gateway 是新一代微服务网关框架,支持多种过滤器实现。本文详解了 `GlobalFilter`、`GatewayFilter` 和 `AbstractGatewayFilterFactory` 三种过滤器的实现方式及其应用场景,帮助开发者高效利用这些工具进行网关开发。
120 1
|
6天前
|
存储 监控 供应链
微服务拆分的 “坑”:实战复盘与避坑指南
本文回顾了从2~3人初创团队到百人技术团队的成长历程,重点讨论了从传统JSP到前后端分离+SpringCloud微服务架构的演变。通过实际案例,总结了微服务拆分过程中常见的两个问题:服务拆分边界不清晰和拆分粒度过细,并提出了优化方案,将11个微服务优化为6个,提高了系统的可维护性和扩展性。
23 0
|
1月前
|
运维 NoSQL Java
后端架构演进:微服务架构的优缺点与实战案例分析
【10月更文挑战第28天】本文探讨了微服务架构与单体架构的优缺点,并通过实战案例分析了微服务架构在实际应用中的表现。微服务架构具有高内聚、低耦合、独立部署等优势,但也面临分布式系统的复杂性和较高的运维成本。通过某电商平台的实际案例,展示了微服务架构在提升系统性能和团队协作效率方面的显著效果,同时也指出了其带来的挑战。
86 4
|
27天前
|
负载均衡 Java API
项目中用的网关Gateway及SpringCloud
Spring Cloud Gateway 是一个功能强大、灵活易用的API网关解决方案。通过配置路由、过滤器、熔断器和限流等功能,可以有效地管理和保护微服务。本文详细介绍了Spring Cloud Gateway的基本概念、配置方法和实际应用,希望能帮助开发者更好地理解和使用这一工具。通过合理使用Spring Cloud Gateway,可以显著提升微服务架构的健壮性和可维护性。
35 0
|
2月前
|
负载均衡 Java API
【Spring Cloud生态】Spring Cloud Gateway基本配置
【Spring Cloud生态】Spring Cloud Gateway基本配置
62 0
|
3月前
|
SpringCloudAlibaba API 开发者
新版-SpringCloud+SpringCloud Alibaba
新版-SpringCloud+SpringCloud Alibaba
|
2天前
|
SpringCloudAlibaba 负载均衡 Dubbo
【SpringCloud Alibaba系列】Dubbo高级特性篇
本章我们介绍Dubbo的常用高级特性,包括序列化、地址缓存、超时与重试机制、多版本、负载均衡。集群容错、服务降级等。
【SpringCloud Alibaba系列】Dubbo高级特性篇
|
2天前
|
存储 SpringCloudAlibaba Java
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论。
【SpringCloud Alibaba系列】一文全面解析Zookeeper安装、常用命令、JavaAPI操作、Watch事件监听、分布式锁、集群搭建、核心理论
|
2天前
|
SpringCloudAlibaba JavaScript Dubbo
【SpringCloud Alibaba系列】Dubbo dubbo-admin安装教程篇
本文介绍了 Dubbo-Admin 的安装和使用步骤。Dubbo-Admin 是一个前后端分离的项目,前端基于 Vue,后端基于 Spring Boot。安装前需确保开发环境(Windows 10)已安装 JDK、Maven 和 Node.js,并在 Linux CentOS 7 上部署 Zookeeper 作为注册中心。
【SpringCloud Alibaba系列】Dubbo dubbo-admin安装教程篇