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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析DNS,个人版 1个月
全局流量管理 GTM,标准版 1个月
简介: 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天前
|
Java 微服务 Spring
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
文章介绍了如何利用Spring Cloud Alibaba快速构建大型电商系统的分布式微服务,包括服务限流降级等主要功能的实现,并通过注解和配置简化了Spring Cloud应用的接入和搭建过程。
SpringBoot+Vue+Spring Cloud Alibaba 实现大型电商系统【分布式微服务实现】
|
29天前
|
资源调度 Java 调度
Spring Cloud Alibaba 集成分布式定时任务调度功能
Spring Cloud Alibaba 发布了 Scheduling 任务调度模块 [#3732]提供了一套开源、轻量级、高可用的定时任务解决方案,帮助您快速开发微服务体系下的分布式定时任务。
14220 19
|
4天前
|
Kubernetes Nacos 微服务
【技术难题破解】Nacos v2.2.3 + K8s 微服务注册:强制删除 Pod 却不消失?!7步排查法+实战代码,手把手教你解决Nacos Pod僵死问题,让服务瞬间满血复活!
【8月更文挑战第15天】Nacos作为微服务注册与配置中心受到欢迎,但有时会遇到“v2.2.3 k8s 微服务注册nacos强制删除 pod不消失”的问题。本文介绍此现象及其解决方法,帮助开发者确保服务稳定运行。首先需检查Pod状态与事件、配置文件及Nacos配置,确认无误后可调整Pod生命周期管理,并检查Kubernetes版本兼容性。若问题持续,考虑使用Finalizers、审查Nacos日志或借助Kubernetes诊断工具。必要时,可尝试手动强制删除Pod。通过系统排查,通常能有效解决此问题。
10 0
|
1月前
|
监控 Java 微服务
Spring Boot微服务部署与监控的实战指南
【7月更文挑战第19天】Spring Boot微服务的部署与监控是保障应用稳定运行和高效维护的重要环节。通过容器化部署和云平台支持,可以实现微服务的快速部署和弹性伸缩。而利用Actuator、Prometheus、Grafana等监控工具,可以实时获取应用的运行状态和性能指标,及时发现并解决问题。在实际操作中,还需根据应用的具体需求和场景,选择合适的部署和监控方案,以达到最佳效果。
|
2月前
|
程序员 测试技术 Docker
黑马程序员2024最新SpringCloud微服务开发与实战 个人学习心得、踩坑、与bug记录Day3 全网最全
黑马程序员2024最新SpringCloud微服务开发与实战 个人学习心得、踩坑、与bug记录Day3 全网最全(1)
171 1
|
2月前
|
JSON Java Spring
实战SpringCloud响应式微服务系列教程(第八章)构建响应式RESTful服务
实战SpringCloud响应式微服务系列教程(第八章)构建响应式RESTful服务
|
2月前
|
关系型数据库 MySQL Shell
黑马程序员2024最新SpringCloud微服务开发与实战 个人学习心得、踩坑、与bug记录Day2 全网最快最全(下)
黑马程序员2024最新SpringCloud微服务开发与实战 个人学习心得、踩坑、与bug记录Day2 全网最快最全(下)
120 0
|
2月前
|
人工智能 Java Spring
使用 Spring Cloud Alibaba AI 构建 RAG 应用
本文介绍了RAG(Retrieval Augmented Generation)技术,它结合了检索和生成模型以提供更准确的AI响应。示例中,数据集(包含啤酒信息)被加载到Redis矢量数据库,Spring Cloud Alibaba AI Starter用于构建一个Spring项目,演示如何在接收到用户查询时检索相关文档并生成回答。代码示例展示了数据加载到Redis以及RAG应用的工作流程,用户可以通过Web API接口进行交互。
52439 66
|
2月前
|
消息中间件 Java 持续交付
Spring Cloud Alibaba 项目搭建步骤和注意事项
Spring Cloud Alibaba 项目搭建步骤和注意事项
415 0
Spring Cloud Alibaba 项目搭建步骤和注意事项
|
2月前
|
存储 SpringCloudAlibaba 关系型数据库
springcloud alibaba(5)
springcloud alibaba
111 0