使用Spring WebClient exchange方法时如何抛出自定义异常
问题
我已实现一个复用方法,通过Spring WebClient的exchange方法返回HTTP调用的响应头、状态码与响应体。现在需要在方法内部捕获4xx和5xx错误并记录响应体,但无法在出现这类错误时返回/抛出自定义异常AutomationException。
当前的复用方法代码如下:
private Mono<ResponseEntity<Mono<String>>> exchangeGet(AutomationHttpRequest request) throws AutomationException { oLogger.debug("Invoking Exchange request for - {}", request.getName()); return webClient .get() .uri(request.getUri()) .headers(request.getHeaderList()) .exchangeToMono(Mono::just) .map( response -> { // Approach 1 // if(response.statusCode().is4xxClientError()){ // oLogger.error("Client side error for request {}, status {}", request.getName(), response.statusCode()); // oLogger.error("response body - "); // response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody()))); // throw new AutomationException(""); // } // // Approach 2 // if(response.statusCode().is4xxClientError()){ // oLogger.error("Client side error for request {}, status {}", request.getName(), response.statusCode()); // oLogger.error("response body - "); // response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody()))); // return Mono.error(new AutomationException("")); // } HttpHeaders headers = response.headers().asHttpHeaders().entrySet().stream() .filter(entry -> !entry.getKey().equalsIgnoreCase("Transfer-Encoding")) .collect(HttpHeaders::new, (httpHeaders, entry) -> httpHeaders.addAll(entry.getKey(), entry.getValue()), HttpHeaders::putAll); return ResponseEntity.status(response.statusCode()) .headers(headers).body(response.bodyToMono(String.class)); }); }
注:AutomationException是自定义异常类。
两种尝试的方案都存在问题:
- 方案1直接抛出异常时,编译器提示错误未处理;
- 方案2返回
Mono.error时,返回类型与方法返回类型不匹配,且因SonarLint规则限制无法使用泛型。
我希望实现的效果和使用retrieve方法的示例一致:
private Mono<String> get(AutomationHttpRequest request) throws AutomationException { oLogger.debug("Invoking GET request for - {}", request.getName()); return webClient .get() .uri(request.getUri()) .headers(request.getHeaderList()) .retrieve() .onStatus(HttpStatus::is4xxClientError, response -> { oLogger.error(LOG_CLIENT_ERROR, request.getName(), response.statusCode()); response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody()))); return Mono.error(new AutomationException(CLIENT_EXCEPTION)); }).onStatus(HttpStatus::is5xxServerError, response -> { oLogger.error(LOG_SERVER_ERROR, request.getName(), response.statusCode()); response.toEntity(String.class).subscribe(error -> oLogger.error(String.valueOf(error.getBody()))); return Mono.error(new AutomationException(SERVER_EXCEPTION)); }) .bodyToMono(String.class); }
该示例通过retrieve方法可分别处理4xx和5xx错误,记录错误状态与响应体,并抛出自定义异常。
需求:
- 使用
exchange方法实现上述相同效果; - 不在消费类中记录异常,统一在复用方法内处理;
- 标准化错误场景下抛出的异常;
- 需要通过
exchange获取响应头和响应体,若有更优实现方式也请告知。
解决方案
核心问题分析
问题根源在于map操作符是同步处理,无法直接返回Mono.error;同时直接抛出异常会被视为同步异常,不符合Reactor的异步错误处理模型。另外,原方法返回Mono<ResponseEntity<Mono<String>>>的嵌套结构不合理,会增加消费端的处理复杂度,建议调整为Mono<ResponseEntity<String>>。
正确实现代码
private Mono<ResponseEntity<String>> exchangeGet(AutomationHttpRequest request) { oLogger.debug("Invoking Exchange request for - {}", request.getName()); return webClient .get() .uri(request.getUri()) .headers(request.getHeaderList()) .exchangeToMono(response -> { // 处理错误状态码 if (response.statusCode().is4xxClientError()) { return handleErrorResponse(response, request.getName(), "客户端异常"); } if (response.statusCode().is5xxServerError()) { return handleErrorResponse(response, request.getName(), "服务端异常"); } // 处理正常响应 HttpHeaders headers = response.headers().asHttpHeaders().entrySet().stream() .filter(entry -> !entry.getKey().equalsIgnoreCase("Transfer-Encoding")) .collect(HttpHeaders::new, (httpHeaders, entry) -> httpHeaders.addAll(entry.getKey(), entry.getValue()), HttpHeaders::putAll); return response.bodyToMono(String.class) .map(body -> ResponseEntity.status(response.statusCode()) .headers(headers) .body(body)); }); } // 抽取错误处理逻辑,复用代码 private Mono<ResponseEntity<String>> handleErrorResponse(ClientResponse response, String requestName, String errorMsg) { oLogger.error("{} 请求错误,状态码: {}", requestName, response.statusCode()); return response.bodyToMono(String.class) .doOnNext(errorBody -> oLogger.error("错误响应体: {}", errorBody)) .flatMap(errorBody -> Mono.error(new AutomationException(errorMsg))); }
关键改进点说明
- 调整返回类型:将
Mono<ResponseEntity<Mono<String>>>改为Mono<ResponseEntity<String>>,避免嵌套Mono,简化消费端处理,同时符合Reactor的异步流模型。 - 使用
exchangeToMono内部处理:直接在exchangeToMono的lambda中处理响应,而不是先Mono::just再map,这样可以直接返回Mono类型的结果,包括错误信号。 - 错误处理异步化:通过
bodyToMono异步获取错误响应体,使用doOnNext记录日志,再通过flatMap转换为Mono.error抛出AutomationException,符合Reactor的错误处理规范。 - 抽取复用逻辑:把错误处理代码抽成单独的
handleErrorResponse方法,提高代码可读性和复用性。 - 移除不必要的
throws AutomationException:Reactor中异常是通过Mono.error传递的,不需要在方法签名上声明受检异常(如果AutomationException是受检异常,建议改为非受检异常,或者在handleErrorResponse中包装)。
额外优化建议
- 如果
AutomationException是受检异常,需要在handleErrorResponse中包装为RuntimeException,或者调整异常类型为非受检,避免破坏Reactor的异步模型。 - 日志记录使用SLF4J的参数化日志,避免字符串拼接。
- 可以考虑统一封装错误处理逻辑为WebClient的过滤器(
ExchangeFilterFunction),进一步复用错误处理逻辑,无需在每个请求方法中重复编写。
内容的提问来源于stack exchange,提问作者Boudhayan Dev
相关产品推荐
相关产品推荐

