【WebFlux】第四篇 —— 响应式异常处理与测试

告别 try-catch:响应式异常处理机制

在传统的同步编程中,我们习惯用 try-catch 来捕获异常。但在响应式编程中,由于代码是声明式的、异步执行的,当异常在 map 或 flatMap 内部抛出时,它不会中断当前线程,而是作为一个"错误信号(onError)"沿着数据流向下游传播。如果这个信号没有被处理,最终会导致整个订阅被终止。

Reactor 提供了一套强大的操作符来优雅地处理这些错误:

  • onErrorReturn(优雅降级):当发生异常时,直接返回一个默认的兜底值,保证流不会中断。

    java 复制代码
    Flux.just(1, 2, 0, 4)
        .map(i -> 10 / i) // 遇到 0 时会抛出 ArithmeticException
        .onErrorReturn(-1) // 捕获异常,返回默认值 -1
        .subscribe(System.out::println); // 输出: 10, 5, -1
  • onErrorResume(切换备用流):当发生异常时,根据异常类型切换到另一个备用的 Publisher(数据流)。这在处理外部服务调用失败时非常有用。

    java 复制代码
    fetchFromPrimaryService()
        .onErrorResume(TimeoutException.class, e -> fetchFromBackupService()) // 主服务超时,自动切换备用服务
        .subscribe(System.out::println);
  • onErrorMap(异常转换):将底层的异常(如数据库报错)转换为业务层更容易理解的自定义异常。

    java 复制代码
    userRepository.findById(id)
        .onErrorMap(DataAccessException.class, e -> new BusinessException("用户数据获取失败"))
        .subscribe();

全局异常拦截:@ControllerAdvice 的响应式改造

除了流级别的处理,Spring WebFlux 依然支持我们熟悉的 @ControllerAdvice 进行全局异常拦截。需要注意的是,在 WebFlux 中,处理方法的返回值应该是响应式的 Mono。

java 复制代码
@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BusinessException.class)
    public Mono<ResponseEntity<ErrorResponse>> handleBusinessException(BusinessException ex) {
        ErrorResponse error = new ErrorResponse(HttpStatus.BAD_REQUEST.value(), ex.getMessage());
        return Mono.just(ResponseEntity.status(HttpStatus.BAD_REQUEST).body(error));
    }
}

响应式自动化测试利器:WebTestClient

在 Spring WebFlux 中,传统的 MockMvc 无法处理异步流,因此官方提供了 WebTestClient。它基于 WebClient 构建,专门用于对 WebFlux 端点进行非阻塞的断言测试。

实战示例:使用 @WebFluxTest 进行控制器测试

java 复制代码
@WebFluxTest(UserController.class) // 仅加载 WebFlux 相关的上下文,启动极快
class UserControllerTest {

    @Autowired
    private WebTestClient webTestClient;

    @MockBean
    private UserService userService; // 模拟 Service 层依赖

    @Test
    void shouldReturnUserWhenExists() {
        // 1. 定义 Mock 行为
        Mockito.when(userService.findById("1"))
               .thenReturn(Mono.just(new User("1", "Alice")));

        // 2. 发起请求并断言
        webTestClient.get().uri("/api/users/1")
            .exchange() // 执行请求
            .expectStatus().isOk() // 断言 HTTP 状态码为 200
            .expectBody() // 断言响应体内容
            .jsonPath("$.name").isEqualTo("Alice");
    }
}

响应式流的单元测试:StepVerifier

如果要测试 Service 层返回的 Flux 或 Mono,WebTestClient 就不适用了。此时我们需要引入 reactor-test 依赖,并使用 StepVerifier。它是验证异步序列的绝对利器。

实战示例:验证 Flux 流的行为

java 复制代码
@Test
void testUserStream() {
    Flux<String> userStream = userService.getActiveUsers();

    StepVerifier.create(userStream)
        .expectNext("Alice")      // 期望第一个元素是 Alice
        .expectNextMatches(name -> name.startsWith("B")) // 期望第二个元素以 B 开头
        .expectNextCount(3)       // 期望接下来还有 3 个元素
        .expectComplete()         // 期望流正常结束
        .verify(Duration.ofSeconds(5)); // 设置超时时间,防止流卡死
}

本篇小结:响应式编程中的异常处理分为"流级别的操作符降级"和"全局级别的统一拦截"。而在测试方面,WebTestClient 和 StepVerifier 是验证响应式逻辑正确性的左膀右臂。

下一步预告:至此,我们已经掌握了 Reactor 的核心语法。下一篇笔记,我们将正式回到 Spring WebFlux 框架,对比"基于注解的 Controller"与"函数式端点(Router Functions)"两种编程模型,并学习如何使用 WebClient 发起非阻塞的外部 HTTP 调用。准备好进入真正的 Web 开发了吗?

相关推荐
良木生香19 小时前
【C++初阶】STL—— Stack & Queue 从入门到精通:容器适配器、迭代器与经典面试题
java·开发语言·c++·算法·zookeeper
xgc_java19 小时前
在Java里把ONNX/OpenCV/FFmpeg跑稳:28篇bytedeco实战小册完整指南
java·opencv·ffmpeg
Lhappy嘻嘻19 小时前
网络(四)|全网最细网络层原理:IP 协议、IP 地址分类、子网划分、路由转发、NAT 穿透详解
java·笔记·网络协议·计算机网络
大模型码小白19 小时前
Milvus 架构设计:从向量索引到分布式检索,AI 原生存储引擎的内部机制
java·大数据·人工智能·分布式·深度学习·milvus
稚南城才子,乌衣巷风流19 小时前
链表(Linked List):数据结构详解与实战应用
java
NWU_LK19 小时前
【WebFlux】第二篇 —— Project Reactor 核心数据类型
java
三84420 小时前
笔记:在同一个物理服务器上通过nginx的虚拟主机生成多个不同的web站点
java·前端·nginx
2501_9327502620 小时前
Android 数据持久化解析
android·java
NWU_LK21 小时前
【WebFlux】第三篇 —— 常用操作符与数据流转换
java