You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring WebClient exchangeToMono无返回数据问题排查求助

问题:Spring WebClient的Mono订阅后无数据返回,processedResponseList始终为空

我用Spring WebClient异步调用外部API,采用exchangeToMono()方法实现,代码如下:

Mono<ExternalServiceResponse> externalServiceResponseMono = webClient.get()
                .header("trackingid", "abc123")
                .exchangeToMono(clientResponse -> {
                    if(clientResponse.statusCode().equals(HttpStatus.OK)) {
                        return clientResponse.bodyToMono(ExternalServiceResponse.class);
                    }
                    else if (clientResponse.statusCode()
                            .is4xxClientError()) {
                        log.error("ErrorResponse Code from ExternalService API is: " + clientResponse.rawStatusCode());
                         return Mono.error(new RestServiceCallException(clientResponse.statusCode().getReasonPhrase()));
                    }else if (clientResponse.statusCode()
                            .is5xxServerError()) {
                        return Mono.error(new ExternalServiceCallException(clientResponse.statusCode().getReasonPhrase()));
                    }
                    return Mono.error(new RuntimeException("Unknown error occured while calling External Service API"));
                })
                .retryWhen(Retry.fixedDelay(2, Duration.ofSeconds(2)).filter(throwable -> throwable instanceof ExternalServiceCallException ));

随后我按如下方式订阅Mono响应:

List<Response> processedResponseList = new ArrayList<>();
externalServiceResponseMono.subscribe(res -> res.getDetails().forEach(section -> processedResponseList.add(processResponseExternalAPI(section))));

Postman验证外部API确实返回了数据,但externalServiceResponseMono始终无数据被订阅,processedResponseList一直为空。请问忽略了什么?为何Mono无数据被订阅?


问题原因及解决方法

核心问题1:异步操作未等待完成

subscribe()是非阻塞异步方法,调用后主线程会继续执行后续逻辑,不会等待Mono的异步调用和回调执行完成。如果你的程序在调用subscribe()后很快终止,Mono的回调(填充集合的逻辑)根本没来得及执行,自然集合为空。

核心问题2:未处理错误信号

当前的subscribe()只处理了正常返回数据的onNext信号,但完全忽略了onError信号。如果调用过程中出现异常(比如反序列化失败、重试触发的异常、未捕获的未知错误等),Mono会直接触发onError,但你没有捕获这个信号,导致异常被静默吞掉,看起来像是“无数据订阅”。

核心问题3:ArrayList线程不安全

即使异步逻辑正常执行,ArrayList不是线程安全集合,在多线程环境下添加元素可能出现数据丢失、索引越界等问题,也会导致集合为空或数据异常。


解决方法

方法1:阻塞等待结果(适合同步场景)

如果业务场景允许同步阻塞,用block()替代subscribe(),强制等待Mono执行完成:

List<Response> processedResponseList = new ArrayList<>();
// 阻塞等待Mono完成并获取结果
ExternalServiceResponse response = externalServiceResponseMono.block();
if (response != null) {
    response.getDetails().forEach(section -> processedResponseList.add(processResponseExternalAPI(section)));
}

方法2:用反应式链处理收集(推荐,符合Reactor编程模型)

不要手动用集合收集数据,而是在反应式流中完成处理和收集,最后再订阅或获取结果:

Mono<List<Response>> processedListMono = externalServiceResponseMono
        .flatMapIterable(ExternalServiceResponse::getDetails) // 拆分Details为单个元素
        .map(this::processResponseExternalAPI) // 处理每个元素
        .collectList(); // 收集为List

// 订阅时同时处理结果和异常
processedListMono.subscribe(
        processedList -> {
            // 处理最终的结果集合
            log.info("处理完成,共{}条数据", processedList.size());
        },
        error -> {
            // 捕获并处理异常
            log.error("调用或处理外部API数据失败", error);
        }
);

// 若需阻塞获取结果,保留block()调用
// List<Response> processedResponseList = processedListMono.block();

方法3:完善subscribe的错误处理+线程安全集合

如果坚持原有订阅方式,必须补充错误处理,同时改用线程安全集合,还要确保主线程不会提前终止:

// 改用线程安全的CopyOnWriteArrayList
List<Response> processedResponseList = new CopyOnWriteArrayList<>();

externalServiceResponseMono.subscribe(
        res -> res.getDetails().forEach(section -> processedResponseList.add(processResponseExternalAPI(section))),
        error -> log.error("订阅外部API响应失败", error) // 新增异常处理
);

// 测试/简单场景下,可添加阻塞等待确保异步逻辑执行完成
try {
    Thread.sleep(5000); // 根据实际API响应时长调整
} catch (InterruptedException e) {
    Thread.currentThread().interrupt();
}

内容的提问来源于stack exchange,提问作者Jawahar Elango

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:45:36