【问题标题】:How to globally handle errors thrown from WebFilter in Spring WebFlux?如何全局处理 Spring WebFlux 中 WebFilter 抛出的错误?
【发布时间】:2022-02-09 13:03:02
【问题描述】:

当从WebFilter 链抛出错误时,如何在 WebFlux 中全局拦截和处理错误

如何处理控制器抛出的错误很清楚:@ControllerAdvice@ExceptionHandler 帮助很大。

WebFilter 组件抛出异常时,此方法不起作用。

在以下配置中,GET /firstGET /second 响应故意引发抛出异常。尽管@ExceptionHandler 方法handleFirsthandleSecond 是相似的,但永远不会调用handleSecond。我想这是因为MyWebFilter 不会让ServerWebExchange 进入可以应用GlobalErrorHandlers 方法的阶段。

回复GET /first

 HTTP 500 "hello first"                   // expected
 HTTP 500 "hello first"                   // actual

回复GET /second

 HTTP 404 "hello second"                                                         // expected
 HTTP 500 {"path": "/second", "status": 500, "error": "Internal Server Error" }  // actual

@RestController
class MyController {

    @GetMapping("/first")
    String first(){
        throw new FirstException("hello first");
    }
}


@Component
class MyWebFilter implements WebFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange swe, WebFilterChain wfc) {
        var path = swe.getRequest().getURI().getPath();
        if (path.contains("second")){
            throw new SecondException("hello second")
        }
    }
}


@ControllerAdvice
class GlobalErrorHandlers {

    @ExceptionHandler(FirstException::class)
    ResponseEntity<String> handleFirst(FirstException ex) {
        return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ex.message)
    }

    @ExceptionHandler(SecondException::class)
    ResponseEntity<String> handleSecond(SecondException ex) {
       return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.message)
    }
}

【问题讨论】:

标签: spring exception spring-webflux


【解决方案1】:

需要三个步骤才能完全控制从应用程序端点处理代码抛出的所有异常:

  1. 实施org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
  2. 使用@ControllerAdvice(或仅@Component)进行注释
  3. @Priority 设置为小于1 以让自定义处理程序在默认处理程序之前运行 (WebFluxResponseStatusExceptionHandler)

棘手的部分是我们得到一个实例实现的地方 ServerResponse.Context 传递给 ServerResponse.writeTo(exchange, context)。没找到决赛 回答,欢迎cmets。在内部 Spring 代码中,它们总是为每个 writeTo 调用创建一个新的上下文实例, 尽管在所有情况下(我已经设法找到)上下文实例都是不可变的。 这就是为什么我最终对所有回复使用相同的ResponseContextInstance。 目前没有检测到这种方法存在问题。


@ControllerAdvice
@Priority(0) /* should go before WebFluxResponseStatusExceptionHandler */
class CustomWebExceptionHandler : ErrorWebExceptionHandler { 

    private val log = logger(CustomWebExceptionHandler::class)

    override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
        log.error("handled ${ex.javaClass.simpleName}", ex)

        val sr = when (ex) {
            is FirstException -> handleFirst(ex) 
            is SecondException -> handleSecond(ex) 
            else -> defaultException(ex)
        }

        return sr.flatMap { it.writeTo(exchange, ResponseContextInstance) }.then()
    }

    private fun handleFirst(ex: FirstException): Mono<ServerResponse> {
        return ServerResponse
                   .status(HttpStatus.INTERNAL_SERVER_ERROR)
                   .bodyValue("first")
    }

    private fun handleSecond(ex: SecondException): Mono<ServerResponse> {
        return ServerResponse.status(HttpStatus.BAD_REQUEST).bodyValue("second")
    }

    private object ResponseContextInstance : ServerResponse.Context {

        val strategies: HandlerStrategies = HandlerStrategies.withDefaults()

        override fun messageWriters(): List<HttpMessageWriter<*>> {
            return strategies.messageWriters()
        }

        override fun viewResolvers(): List<ViewResolver> {
            return strategies.viewResolvers()
        }
    }
}

【讨论】:

    猜你喜欢
    • 2019-11-08
    • 2020-10-31
    • 2021-08-04
    • 2021-12-29
    • 2021-08-06
    • 1970-01-01
    • 2018-07-05
    • 2020-03-23
    • 2019-03-11
    相关资源
    最近更新 更多