【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 开发了吗?

相关推荐
聚美智数5 小时前
智能结构化识别-图片结构解析-图像结构化解析-照片结构化解析
android·java·数据库
十年Java程序媛5 小时前
Java枚举Enum完整教程|替代静态常量,开发与面试要点
java·开发语言·spring boot·后端·面试
我是人✓5 小时前
SpringBoot静态资源映射揭秘
java·spring boot·后端
土豆.exe6 小时前
nf_tables 的 verdict 被读了两次:一个十年未闭合的位域错配
java·数据库·网络安全·缓存
小坏讲微服务7 小时前
刚发布一个月的JDK 27,为什么我建议你立刻升级?
java·开发语言·spring boot·spring·架构·求职招聘·springboot4
kunicoding7 小时前
多线程编程核心概念详解:从线程基础到并发安全
java·jvm·安全
snow@li8 小时前
SpringBoot:核心注解全景梳理 / @xxx 叫“注解”(Annotation)
java·spring
devilnumber8 小时前
避坑_IDEA 提取方法(Extract Method)工具
java·ide·intellij-idea
鬼手点金8 小时前
Agent 的学习机制:后训练、上下文学习与外部化学习
java·人工智能·git·学习·自动化·nanogpt
小陈不好吃8 小时前
equals 与 hashCode 的契约关系:从 HashMap 到 Lombok 注解
java·hashmap·lombok