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
相关产品推荐
相关产品推荐

