spring boot 全局异常处理捕获 reactor 订阅异常,核心在于将 reactive 链中的错误通过 onerrorresume 或 onerrormap 转换为 spring mvc 可识别的异常,或者在 webflux 环境下利用 webexceptionhandler。对于基于 servlet 的 spring boot 应用,reactor 的异步异常通常不会直接触发 @controlleradvice,必须手动桥接;而在纯 webflux 应用中,需确保异常被抛出到响应式流中并被框架拦截。

Servlet 环境下如何将 Mono 错误转为 HTTP 响应
在传统的 Spring MVC(Servlet)环境中,@RestControllerAdvice 无法自动捕获 Controller 内部返回的 Mono 或 Flux 产生的订阅期异常。因为 Controller 方法本身同步返回了一个对象(即 Publisher),而真正的执行发生在后续线程。要解决这个问题,需要在 Controller 层使用 block() 阻塞等待结果并让异常向上抛出,或者使用自定义的 ResponseBodyAdvice 进行拦截处理。最推荐的做法是在 Service 层或 Controller 层显式处理错误,将其转换为标准异常。

import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Mono;
@RestController
public class DemoController {
@GetMapping("/data")
public Mono<String> getData() {
return Mono.just("value")
.flatMap(s -> {
// 模拟业务逻辑产生异常
if (true) throw new RuntimeException("Simulated Error");
return Mono.just(s);
})
.onErrorResume(e -> {
// 关键:将 Reactor 异常转换为 Spring 可管理的异常
// 这样可以在 @ControllerAdvice 中统一捕获
return Mono.error(new CustomBusinessException("Handled Error", e));
});
}
}
上述代码展示了如何在 Reactive 链中通过 onErrorResume 捕获底层异常,并将其包装为自定义异常 CustomBusinessException。这样,即使是在 Servlet 容器中,如果配合适当的配置或使用 block()(不推荐用于生产环境高并发场景),或者更常见的是在 WebFlux 中,这个异常就能被全局处理器捕获。在实际的 Servlet 项目中,通常建议避免在 Controller 中直接返回未处理的 Mono,而是通过 DeferredResult 或 ListenableFuture 来桥接,或者确保所有异步操作都有明确的错误边界。
WebFlux 环境下如何配置全局异常处理器
在 Spring WebFlux 应用中,异常处理机制与 Servlet 不同。WebFlux 使用 WebExceptionHandler 接口来处理响应式流中的错误。要实现全局异常处理,需要实现该接口并注册为 Bean。当 Reactive 链中出现未处理的异常时,WebFlux 框架会自动调用这些处理器。开发者可以定义多个处理器,并通过 @Order 注解控制优先级。此外,还可以使用 @ExceptionHandler 注解在 Controller 级别处理特定异常,但在全局层面,实现 WebExceptionHandler 是更通用的方式。
将 React 应用升级到最新版本,从类组件迁移到 Hooks,并采用并发特性。用于现代化改造 React 代码库、迁移到 React Hooks 或升级到最新

import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.core.annotation.Order;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
@Component
@Order(-2) // 确保比默认处理器优先执行
public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
byte[] bytes = "{\"error\":\"Internal Server Error\"}".getBytes();
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
exchange.getResponse().setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Mono.just(buffer));
}
}
这段代码定义了一个全局异常处理器,它拦截所有未被处理的异常,并返回一个 JSON 格式的错误响应。通过设置 @Order(-2),确保它在 Spring Boot 默认的错误处理器之前执行。在实际开发中,可以根据异常类型定制不同的响应状态码和消息体。例如,可以检查异常是否为 CustomBusinessException,如果是,则返回 400 Bad Request 并附带具体错误信息。这种机制确保了无论 Reactive 链中何处发生异常,用户都能收到一致且友好的错误反馈。
如何处理 Mono 链中特定条件的空值情况
在 Reactive 编程中,除了异常,还需要处理数据流中的空值或缺失数据。当 Mono 为空(即没有发出任何元素)时,可以通过 defaultIfEmpty 或 switchIfEmpty 提供默认值或替代流程。这与异常处理不同,空值通常被视为正常的数据流一部分,而不是错误状态。然而,在某些业务场景下,空值可能意味着数据缺失,需要特殊处理。例如,查询数据库时若未找到记录,返回空 Mono,此时可能需要返回一个默认的 DTO 或抛出异常以提示前端。
import reactor.core.publisher.Mono;
public class EmptyHandlingExample {
public Mono<String> findUserById(Long id) {
return Mono.justOrEmpty(getUserFromDB(id)) // 假设 getUserFromDB 返回 Optional
.switchIfEmpty(Mono.error(new UserNotFoundException("User not found")))
.map(user -> user.getName());
}
}
上述示例展示了如何使用 switchIfEmpty 将空 Mono 转换为异常。如果 getUserFromDB 返回空,switchIfEmpty 会触发提供的 Mono.error,从而将空值情况转化为异常,进而可以被全局异常处理器捕获。这种方式保持了错误处理的一致性,避免了在业务逻辑中分散处理空值的复杂性。开发者应根据业务需求选择是直接返回默认值还是抛出异常,以确保 API 行为的可预测性。

















