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

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

OutputStream out = response.getOutputStream();
out.write("输出的内容");
out.flush();

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

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

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

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内容如下:

{
    
    
    "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
在这里插入图片描述

代码如下:

@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方法,分析见注释

@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方法如下

@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里面看看

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

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

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

{
    
    
    "returnCode": "ERROR",
    "errorMsg": "参数异常",
    "body": null
}

我们实际上可以继承DefaultErrorWebExceptionHandler,并且实现其renderErrorResponse方法就可以了。
可以看到DefaultErrorWebExceptionHandler,是通过下面的方式注入到容器的,如果我们也定义一个也注册到容器,那么就会覆盖原有的实现
在这里插入图片描述

整体流程图如下:

在这里插入图片描述

三、自定义异常处理器

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

@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、自定义异常处理器

/**
 * 自定义异常处理器
 */
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、注册异常处理器

@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、请求效果
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/suyuaidan/article/details/132663141