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

相关推荐
·薯条大王8 小时前
经济实惠玩云服务器|一台云服务器多人共用,子账号配置教程
java·linux·运维·服务器·汇编·c++·python
ZJH__GO11 小时前
网络编程v4pro--实现聊天室文件传输功能
java·服务器·网络·计算机网络
程序员黑豆11 小时前
Windows 系统 Java 环境变量配置全攻略:解决“不是内部或外部命令”
java·前端·ai编程
命运之光11 小时前
【C语言完整代码】就诊信息管理系统
java·c语言·开发语言
凤山老林11 小时前
Spring Boot @Async 线上实战:从默认配置到生产级线程池治理
java·spring boot·后端
marvelyu12 小时前
每天10分钟学会OceanBase系列(Day 20):跨机房容灾实战——构建多数据中心高可用架构
java·大数据·数据库
AI人工智能+电脑小能手13 小时前
大白话说Java设计模式-04-工厂方法模式(业务实战篇)
java·设计模式·工厂方法模式·架构设计·代码解耦
BUG指挥官13 小时前
Sa-Token和Spring Security对比
java·后端·spring
小当家.10514 小时前
深入理解 ReAct Agent:从原理到 Java 实战
java·react.js·agent·react·架构设计·agent设计
geminigoth14 小时前
Spring AI Alibaba 入门开发一(备份)
java·人工智能·spring