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

如何让Spring Reactive WebClient的所有异常在Mono订阅时抛出?

问题:让WebClient构建阶段的异常延迟到订阅时抛出

代码示例

我使用Spring响应式WebClient连接Web服务,代码如下:

@Autowired
private org.springframework.web.reactive.function.client.WebClient webClient;

public Mono<MyData> getMyData(MyRequest request) {
    return webClient
        .get()
        .uri(uriBuilder -> this.createUri(uriBuilder, request))
        // 此步骤之前抛出的异常不在Mono上下文内
        .retrieve()
        .bodyToMono(MyDataDto.class)
        .map(myDataDto -> mapToMyData(myDataDto))
        ;
}

createUri()方法可能抛出异常:

private URI createUri(UriBuilder uriBuilder, MyRequest request) {
    if (somethingIsInconsistent) {
        throw new MyException();
    }
}

问题是该异常会在Mono创建时的主线程抛出,而非Mono订阅时的上下文环境中。

测试情况

因此以下测试用例失败:

@Test
void getMyData_whenBadRequest_throwExceptionWhenSubscribed() {
    service.getMyData(badRequest)
        .as(StepVerifier::create)
        .expectError(MyException.class)
        .verify();
}

而以下测试用例通过:

@Test
void getMyData_whenBadRequest_throwExceptionWhenCreated() {
    assertThrows(MyException.class, 
        () -> service.getMyData(badRequest));
}

我希望所有异常都在Mono订阅的上下文中被捕获。

问题问询

是否有优雅的Reactive WebClient写法,可包装调用逻辑让所有异常在Mono订阅时抛出?我目前的方案如下:

public Mono<MyData> getMyData(MyRequest request) {
    return Mono.defer(() -> webClient
            .get()
            .uri(uriBuilder -> this.createUri(uriBuilder, req))
            .retrieve()
            .bodyToMono(MyDataDto.class)
         )
        .map(myDataDto -> mapToMyData(myDataDto))
        ;
}

但该写法生硬不美观,原代码的可读性和流畅性更好!

注:我清楚问题原因是异常发生在RequestHeaderSpec构建阶段,并非询问原因。URI需根据request动态生成,参数无效时请求应失败,且无更合理的生成位置。


解决方案

可以用两种更贴合响应式风格的写法替代Mono.defer的生硬包裹:

方式1:用Mono.fromCallable包装URI生成逻辑

把createUri的逻辑放到Mono.fromCallable中捕获异常,再通过flatMap衔接WebClient请求,既保留链式调用的流畅性,又将异常延迟到订阅阶段:

public Mono<MyData> getMyData(MyRequest request) {
    return Mono.fromCallable(() -> createUri(webClient.getUriBuilderFactory().builder(), request))
            .flatMap(uri -> webClient.get()
                    .uri(uri)
                    .retrieve()
                    .bodyToMono(MyDataDto.class))
            .map(this::mapToMyData);
}

方式2:让createUri返回Mono<URI>

直接将createUri改造为返回响应式类型,异常通过Mono.error()抛出,完全遵循响应式编程理念:

private Mono<URI> createUri(UriBuilder uriBuilder, MyRequest request) {
    if (somethingIsInconsistent) {
        return Mono.error(new MyException());
    }
    // 正常生成URI并返回
    return Mono.just(uriBuilder.build(...));
}

public Mono<MyData> getMyData(MyRequest request) {
    return createUri(webClient.getUriBuilderFactory().builder(), request)
            .flatMap(uri -> webClient.get()
                    .uri(uri)
                    .retrieve()
                    .bodyToMono(MyDataDto.class))
            .map(this::mapToMyData);
}

这两种写法都避免了嵌套包裹,保持了代码的可读性和流畅性,同时实现了所有异常在订阅时抛出的需求。


内容的提问来源于stack exchange,提问作者Honza Zidek

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 20:34:53