【深入解析spring cloud gateway】07 自定义异常返回报文

Servlet的HttpResponse对象,返回响应报文,一般是这么写的,通过输出流直接就可以将返回报文输出。

java 复制代码
OutputStream out = response.getOutputStream();
out.write("输出的内容");
out.flush();

在filter中如果发生异常(例如请求参数不合法),抛出异常信息的时候,调用方收到的返回码和body都是Spring Cloud Gateway框架处理来处理的。这一节我们分析一下,gateway的异常返回报文是怎么返回的,并定义一个自己的异常返回报文格式。

一、先定义一个Filter,直接抛出异常

定义一个直接抛出异常的filter

java 复制代码
public class ExceptionFilter implements GlobalFilter, Ordered {

    @Override    
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        throw new IllegalArgumentException("参数不合法");    }

    @Override    
    public int getOrder() {
        return 0;    
    }
}

异常抛出如下图

json内容如下:

json 复制代码
{
    "timestamp": "2023-08-28T03:55:02.380+00:00",
    "path": "/hello-service/hello",
    "status": 500,
    "error": "Internal Server Error",
    "requestId": "0204dca5-1"
}

二、源码分析

上节我们分析了核心流程。在整个核心流程中,我们并没有关注有异常的情况。

入口HttpWebHandlerAdapter调用的delegate实际上就是:DefaultErrorWebExceptionHandler

代码如下:

java 复制代码
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
    //省略部分代码
    return getDelegate().handle(exchange)
            .doOnSuccess(aVoid -> logResponse(exchange))
            .onErrorResume(ex -> handleUnresolvedError(exchange, ex))
            .then(Mono.defer(response::setComplete));
}

进入DefaultErrorWebExceptionHandler的handle方法,分析见注释

java 复制代码
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
    Mono<Void> completion;
    try {
 //正常的处理流程
        completion = super.handle(exchange);
    }
    catch (Throwable ex) {
        completion = Mono.error(ex);
    }
//产生异常的情况,由异常处理器来进行处理
    for (WebExceptionHandler handler : this.exceptionHandlers) {
        completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
    }
    return completion;
}

如果产生异常的情况,由异常处理器来进行处理,这个异常处理器是一个列表。

而异常处理器最核心的就是这个:DefaultErrorWebExceptionHandler

其handle方法如下

java 复制代码
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable throwable) {
    if (exchange.getResponse().isCommitted() || isDisconnectedClientError(throwable)) {
        return Mono.error(throwable);
    }
    this.errorAttributes.storeErrorInformation(throwable, exchange);
    ServerRequest request = ServerRequest.create(exchange, this.messageReaders);
    return getRoutingFunction(this.errorAttributes).route(request)
        .switchIfEmpty(Mono.error(throwable))
        .flatMap((handler) -> handler.handle(request))
        .doOnNext((response) -> logError(request, response, throwable))
        .flatMap((response) -> write(exchange, response));
}

跟到getRoutingFunction里面看看

java 复制代码
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
    return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
}

最终跟到下面的这个方法:renderErrorResponse,从下面的截图可以看到,error Map这个对象,正是报文体的格式

如果我们想自定义一个异常响应的返回报文,如下,应该怎么弄呢?

json 复制代码
{
    "returnCode": "ERROR",
    "errorMsg": "参数异常",
    "body": null
}

我们实际上可以继承DefaultErrorWebExceptionHandler,并且实现其renderErrorResponse方法就可以了。

可以看到DefaultErrorWebExceptionHandler,是通过下面的方式注入到容器的,如果我们也定义一个也注册到容器,那么就会覆盖原有的实现

整体流程图如下:

三、自定义异常处理器

1、定义一个产生异常的filter,模拟产生异常

java 复制代码
@Slf4j
public class ExceptionFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("filter产生了异常");
        throw new IllegalArgumentException("参数不合法");
    }

    @Override
    public int getOrder() {
        return 0;
    }
}

2、自定义异常处理器

java 复制代码
/**
 * 自定义异常处理器
 */
public class CustomErrorWebExceptionHandler extends DefaultErrorWebExceptionHandler {

    public CustomErrorWebExceptionHandler(ErrorAttributes errorAttributes, WebProperties.Resources resources, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resources, errorProperties, applicationContext);
    }

    @Override
    protected Mono<ServerResponse> renderErrorResponse(ServerRequest request) {
        // 最终是用responseBodyMap来生成响应body的
        Map<String, Object> responseBodyMap = new HashMap<>();

        // 这里和父类的做法一样,取得DefaultErrorAttributes整理出来的所有异常信息
        Map<String, Object> error = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));

        // 原始的异常信息可以用getError方法取得
        Throwable throwable = getError(request);

        responseBodyMap.put("returnCode", "my error code");
        responseBodyMap.put("errorMsg", throwable.getMessage());
        responseBodyMap.put("body", null);
        return ServerResponse
                // http返回码
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                // 类型和以前一样
                .contentType(MediaType.APPLICATION_JSON)
                // 响应body的内容
                .body(BodyInserters.fromValue(responseBodyMap));
    }
}

3、注册异常处理器

java 复制代码
@Configuration(proxyBeanMethods = false)
public class ExceptionHandlerConfig {
    private final ServerProperties serverProperties;

    public ExceptionHandlerConfig(ServerProperties serverProperties) {
        this.serverProperties = serverProperties;
    }

    @Bean
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes,
                                                             WebProperties webProperties, ObjectProvider<ViewResolver> viewResolvers,
                                                             ServerCodecConfigurer serverCodecConfigurer, ApplicationContext applicationContext) {
        CustomErrorWebExceptionHandler exceptionHandler = new CustomErrorWebExceptionHandler(errorAttributes,
                webProperties.getResources(), this.serverProperties.getError(), applicationContext);
        exceptionHandler.setViewResolvers(viewResolvers.orderedStream().collect(Collectors.toList()));
        exceptionHandler.setMessageWriters(serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

4、请求效果

相关推荐
Wx-bishekaifayuan1 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Stringzhua3 小时前
【SpringCloud】Kafka消息中间件
spring·spring cloud·kafka
想进大厂的小王8 小时前
项目架构介绍以及Spring cloud、redis、mq 等组件的基本认识
redis·分布式·后端·spring cloud·微服务·架构
customer089 小时前
【开源免费】基于SpringBoot+Vue.JS医院管理系统(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·开源·intellij-idea
杨荧9 小时前
【JAVA毕业设计】基于Vue和SpringBoot的服装商城系统学科竞赛管理系统
java·开发语言·vue.js·spring boot·spring cloud·java-ee·kafka
牛角上的男孩11 小时前
Istio Gateway发布服务
云原生·gateway·istio
aloha_7891 天前
从零记录搭建一个干净的mybatis环境
java·笔记·spring·spring cloud·maven·mybatis·springboot
茶馆大橘1 天前
微服务系列五:避免雪崩问题的限流、隔离、熔断措施
java·jmeter·spring cloud·微服务·云原生·架构·sentinel