Spring WebFlux中WebClient调用捕获服务端特定异常问题
问题解答
核心原因
服务B抛出的DatabaseFetchException是Java进程内异常,无法直接通过HTTP调用跨进程传递给服务A。当服务B抛出该异常时,Spring MVC会自动将其转换为HTTP错误响应(默认是500状态码),服务A的WebClient收到这个HTTP错误后,会抛出自身的异常(比如WebClientResponseException),而非服务B的DatabaseFetchException,所以你写的catch(DatabaseFetchException e)捕获块根本不会触发,最终进入通用Exception捕获块。
解决方案
要让服务A能识别并捕获服务B的自定义异常,需要分两步处理:
1. 服务B:将自定义异常转换为带标识的HTTP响应
通过全局异常处理器,把DatabaseFetchException转换成包含异常标识的HTTP响应,让服务A能识别:
@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(DatabaseFetchException.class) public ResponseEntity<String> handleDatabaseFetchException(DatabaseFetchException ex) { // 返回500状态码,响应体携带异常类型标识和消息 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("DatabaseFetchException:" + ex.getMessage()); } }
2. 服务A:处理HTTP错误并手动抛出目标异常
修改WebClient的调用逻辑,在收到服务B的错误响应时,解析响应体中的标识,手动抛出DatabaseFetchException,这样就能被对应的捕获块捕获:
public <T> T callExternalApi(String uri, Class<T> responseType) throws DatabaseFetchException { try { return webClient.get() .uri(uri) .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) .retrieve() // 拦截服务端5xx错误响应 .onStatus(HttpStatus::is5xxServerError, clientResponse -> { return clientResponse.bodyToMono(String.class) .flatMap(responseBody -> { // 根据响应体中的标识判断是否为目标异常 if (responseBody != null && responseBody.startsWith("DatabaseFetchException:")) { String errorMsg = responseBody.substring("DatabaseFetchException:".length()); return Mono.error(new DatabaseFetchException(errorMsg)); } // 其他服务端错误抛出通用异常 return Mono.error(new RuntimeException("Server error: " + responseBody)); }); }) .bodyToMono(responseType) .block(); } catch (DatabaseFetchException e) { System.out.println("Database Exception occurred"); throw e; // 可根据业务需求选择重新抛出或处理后返回 } catch (Exception e) { System.out.println("Generic Exception occurred"); return null; } }
补充说明
如果需要更规范的异常传递,也可以定义统一的错误响应DTO(比如包含errorCode、errorMessage字段),服务B返回这个DTO,服务A解析后根据errorCode判断是否抛出DatabaseFetchException,这样比字符串匹配更可靠。
内容的提问来源于stack exchange,提问作者Arunabha Ghosh
相关产品推荐
相关产品推荐

