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

