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


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

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

    在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;    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    异常抛出如下图
    在这里插入图片描述

    json内容如下:

    {
        "timestamp": "2023-08-28T03:55:02.380+00:00",
        "path": "/hello-service/hello",
        "status": 500,
        "error": "Internal Server Error",
        "requestId": "0204dca5-1"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    二、源码分析

    上节我们分析了核心流程。在整个核心流程中,我们并没有关注有异常的情况。
    入口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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    进入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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    如果产生异常的情况,由异常处理器来进行处理,这个异常处理器是一个列表。
    而异常处理器最核心的就是这个: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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    跟到getRoutingFunction里面看看

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

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

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

    {
        "returnCode": "ERROR",
        "errorMsg": "参数异常",
        "body": null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们实际上可以继承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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    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));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4、请求效果
    在这里插入图片描述

  • 相关阅读:
    win10上使用VS2017编译libcurl
    RabbitMQ-延迟队列
    【数据结构】时间复杂度和空间复杂度
    CSS的美化(文字、背景) Day02
    记某同事的两次误操作导致Linux瘫痪
    驱动开发,stm32mp157a开发板的led灯控制实验(再优化),使用ioctl函数,通过字符设备驱动分步注册方式编写LED驱动,完成设备文件和设备的绑定
    元宇宙将会越来越多地与行业、场景联系在一起
    python爬虫
    【STM32基础 CubeMX】PWM输出
    【Java并发编程】——线程池
  • 原文地址:https://blog.csdn.net/suyuaidan/article/details/132663141