1. 概述

本文将深入探讨在 Spring WebFlux 项目中处理错误的多种策略,并通过一个实际案例进行演示。

我们会分析不同场景下哪种错误处理方式更合适,帮助你在实际开发中做出合理选择。文末附有完整的源码链接,方便查阅。

2. 示例准备

Maven 依赖配置与我们之前介绍 Spring WebFlux 入门 的文章一致,这里不再赘述。

本次示例将构建一个 RESTful 接口,接收 username 查询参数,并返回 "Hello username" 字符串。

首先,定义一个路由函数,将 /hello 的 GET 请求映射到处理器的 handleRequest 方法:

@Bean
public RouterFunction<ServerResponse> routes(Handler handler) {
    return RouterFunctions.route(RequestPredicates.GET("/hello")
      .and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), 
        handler::handleRequest);
}

接着,实现 handleRequest 方法,调用 sayHello() 并将其结果封装进 ServerResponse

public Mono<ServerResponse> handleWithGlobalErrorHandler(ServerRequest request) {
    return 
      //...
        sayHello(request)
      //...
}

最后是 sayHello() 方法,它负责拼接字符串:

private Mono<String> sayHello(ServerRequest request) {
    try {
        return Mono.just("Hello, " + request.queryParam("name").get());
    } catch (Exception e) {
        return Mono.error(e);
    }
}

只要请求中包含 username 参数(例如 /hello?name=Tonni),接口就能正常工作。
⚠️ 但如果请求不带参数(如 /hello),就会抛出 NoSuchElementException

接下来,我们就来看看如何在 WebFlux 中优雅地处理这类异常。

3. 函数式层级的错误处理

MonoFlux 提供了两个核心操作符用于函数式错误处理,它们足够灵活,适用于基于注解和函数式路由(RouterFunction)两种编程模型。

3.1 使用 onErrorReturn 返回默认值

适用场景:出错时返回一个静态默认值,简单粗暴。

public Mono<ServerResponse> handleWithErrorReturn(ServerRequest request) {
    return sayHello(request)
      .onErrorReturn("Hello Stranger")
      .flatMap(s -> ServerResponse.ok()
        .contentType(MediaType.TEXT_PLAIN)
        .bodyValue(s));
}

sayHello() 抛异常时,直接返回 "Hello Stranger"。适合对容错要求不高、可降级的场景。

3.2 使用 onErrorResume 实现更复杂恢复逻辑

onErrorResume 更强大,支持三种典型用法:

✅ 动态生成 fallback 值

public Mono<ServerResponse> handleWithErrorResumeAndDynamicFallback(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
        .contentType(MediaType.TEXT_PLAIN)
        .bodyValue(s))
      .onErrorResume(e -> Mono.just("Error " + e.getMessage())
        .flatMap(s -> ServerResponse.ok()
          .contentType(MediaType.TEXT_PLAIN)
          .bodyValue(s)));
}

返回包含具体错误信息的响应,便于调试,但注意不要把敏感信息暴露给前端。

✅ 调用备用方法(Fallback Method)

public Mono<ServerResponse> handleWithErrorResumeAndFallbackMethod(ServerRequest request) {
    return sayHello(request)
      .flatMap(s -> ServerResponse.ok()
        .contentType(MediaType.TEXT_PLAIN)
        .bodyValue(s))
      .onErrorResume(e -> sayHelloFallback()
        .flatMap(s -> ServerResponse.ok()
        .contentType(MediaType.TEXT_PLAIN)
        .bodyValue(s)));
}

出错时切换到备用逻辑,比如访问本地缓存或默认用户。这是典型的“服务降级”思路。

✅ 捕获、包装并重新抛出异常

public Mono<ServerResponse> handleWithErrorResumeAndCustomException(ServerRequest request) {
    return ServerResponse.ok()
      .body(sayHello(request)
      .onErrorResume(e -> Mono.error(new NameRequiredException(
        HttpStatus.BAD_REQUEST, 
        "username is required", e))), String.class);
}

将底层异常包装为业务异常(如 NameRequiredException),便于后续统一处理。这是推荐做法,尤其在复杂业务流中。

⚠️ 注意:这里只是抛出异常,真正的处理仍需全局异常机制配合,见下文。

4. 全局错误处理

前面的函数式处理适合局部、特定逻辑的容错。但在实际项目中,我们更需要一个统一的全局异常处理机制,避免到处写 onErrorResume

Spring WebFlux 提供了 AbstractErrorWebExceptionHandler,让我们可以自定义全局错误响应。

实现步骤如下:

步骤一:自定义错误属性(Error Attributes)

继承 DefaultErrorAttributes,重写 getErrorAttributes() 方法,控制返回给客户端的错误信息:

public class GlobalErrorAttributes extends DefaultErrorAttributes{
    
    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, 
      ErrorAttributeOptions options) {
        Map<String, Object> map = super.getErrorAttributes(
          request, options);
        map.put("status", HttpStatus.BAD_REQUEST);
        map.put("message", "username is required");
        return map;
    }
}

这样,当异常发生时,响应体中会包含我们指定的 statusmessage 字段。

步骤二:实现全局异常处理器

创建一个组件继承 AbstractErrorWebExceptionHandler

@Component
@Order(-2)
public class GlobalErrorWebExceptionHandler extends 
    AbstractErrorWebExceptionHandler {

    // constructors

    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(
      ErrorAttributes errorAttributes) {

        return RouterFunctions.route(
          RequestPredicates.all(), this::renderErrorResponse);
    }

    private Mono<ServerResponse> renderErrorResponse(
       ServerRequest request) {

       Map<String, Object> errorPropertiesMap = getErrorAttributes(request, 
         ErrorAttributeOptions.defaults());

       return ServerResponse.status(HttpStatus.BAD_REQUEST)
         .contentType(MediaType.APPLICATION_JSON)
         .body(BodyInserters.fromValue(errorPropertiesMap));
    }
}

关键点说明:

  • @Order(-2):确保优先级高于 Spring Boot 默认的 DefaultErrorWebExceptionHandler(默认 @Order(-1))。
  • getRoutingFunction():将所有错误请求路由到 renderErrorResponse 方法。
  • renderErrorResponse():构造包含错误信息的 JSON 响应,状态码为 400。

✅ 最终效果:

  • 对 API 客户端返回结构化 JSON 错误信息
  • 对浏览器返回“白页”错误页面(可进一步定制)

5. 总结

本文介绍了 Spring WebFlux 中两类错误处理方式:

策略 适用场景 推荐度
onErrorReturn 简单降级,返回默认值 ⭐⭐
onErrorResume 动态 fallback、调用备用逻辑、包装异常 ⭐⭐⭐⭐
全局异常处理器 统一错误格式、避免重复代码 ⭐⭐⭐⭐⭐

最佳实践建议

  • 局部错误用 onErrorResume 包装为业务异常
  • 全局统一通过 AbstractErrorWebExceptionHandler 处理并返回标准格式
  • 避免在 onErrorReturn 中暴露敏感信息

💡 源码已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/spring-reactive-modules/spring-reactive


原始标题:Handling Errors in Spring WebFlux | Baeldung