如何监听Reactor Netty触发doOnError的原因并避免Mono触发doOnCancel
问题分析
你遇到的doOnCancel触发是WebFlux的正常行为:当浏览器刷新/断开HTTP连接时,WebFlux会感知到TCP通道关闭,进而取消当前请求对应的Mono订阅,触发取消回调。下面针对你的需求给出具体解决方案:
一、监听Reactor Netty的错误原因
要捕获Reactor Netty底层的错误,可以从全局配置和Mono链内捕获两个层面入手:
1. 全局Netty错误监听(WebFlux配置类)
通过定制Netty服务器的连接处理器,捕获所有底层通道错误:
@Configuration public class NettyErrorConfig { @Bean public WebServerFactoryCustomizer<NettyReactiveWebServerFactory> nettyErrorListener() { return factory -> factory.addServerCustomizers(server -> server.doOnConnection(conn -> conn.addHandlerLast(new ChannelDuplexHandler() { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 打印Netty底层错误详情,包含连接断开、通道异常等信息 System.err.printf("Reactor Netty 底层错误: %s%n", cause.getMessage()); // 继续传递异常,不影响原有错误处理逻辑 ctx.fireExceptionCaught(cause); } }) ) ); } }
2. 在Mono链内捕获特定错误
针对HTTP请求链路,直接在Mono中判断并捕获Netty相关异常:
@GetMapping("/api/testOnCancel") public Mono<String> testOnCancel() { return Mono.just("1") .flatMap(str -> Mono.just("test" + str)) .delayElement(Duration.ofMinutes(2)) .doOnCancel(() -> System.out.println("on cancel")) .doOnSuccess(str -> System.out.println("success")) .doOnError(throwable -> { // 识别连接断开类错误(如IOException是TCP连接关闭的常见异常) if (throwable instanceof IOException || (throwable.getCause() != null && throwable.getCause() instanceof IOException)) { System.err.printf("连接断开错误: %s%n", throwable.getMessage()); } else { System.err.printf("业务逻辑错误: %s%n", throwable.getMessage()); } }) .flatMap(str -> Mono.just("byby" + str)); }
二、避免Mono因浏览器中断触发doOnCancel
核心思路是将业务逻辑与HTTP请求的订阅生命周期解耦,让后台任务不受HTTP连接状态影响:
方案1:使用detach()分离订阅上下文
通过detach()让Mono的执行脱离当前HTTP请求的订阅上下文,即使浏览器断开连接,后台任务仍会继续执行,且不会触发doOnCancel:
@GetMapping("/api/testOnCancel") public Mono<String> testOnCancel() { // 定义独立的后台任务 Mono<String> backgroundTask = Mono.just("1") .flatMap(str -> Mono.just("test" + str)) .delayElement(Duration.ofMinutes(2)) .doOnSuccess(str -> System.out.println("后台任务执行完成: " + str)) .doOnError(System.err::println) .flatMap(str -> Mono.just("byby" + str)) // 分离订阅上下文,HTTP断开不会取消此任务 .detach() // 放到弹性线程池执行,避免阻塞IO线程 .subscribeOn(Schedulers.boundedElastic()); // HTTP接口立即返回响应,后台任务异步执行 return Mono.just("请求已受理,后台正在处理") .doOnSuccess(v -> backgroundTask.subscribe()); }
方案2:使用消息队列彻底解耦
如果业务允许,将耗时任务提交到消息队列(如Redis Stream、RabbitMQ),HTTP接口直接返回确认,后台服务消费队列执行任务,完全脱离HTTP请求生命周期。
内容的提问来源于stack exchange,提问作者dubbing youtube
相关产品推荐
相关产品推荐

