嵌套调用supplyAsync时如何处理受检/非受检异常并传播至控制器层
核心问题原因
你当前的异常无法向上传播到控制器层的核心原因是:Helper类的异步任务内部捕获了所有异常后仅打印堆栈、返回默认false值,没有将异常传递到返回的CompletableFuture实例中,上层调用自然感知不到异常。
解决方案
1. 修改Helper类的异常处理逻辑
CompletableFuture.supplyAsync入参的Supplier函数接口不允许抛出受检异常,因此捕获到受检异常后需要包装为CompletionException(Java异步场景专用的未受检异常包装类)抛出,抛出后异常会自动存入CompletableFuture的异常结果中,上层调用get()/join()时就能拿到对应的异常。
修改后的Helper类代码示例:
@Component @AllArgsConstructor public class Helper { private final Executor executor; private final StringValidator stringValidator; private final TerminologyService terminologyService; public CompletableFuture<Boolean> codeableConceptValidator(String code, String system) { return CompletableFuture.supplyAsync(() -> { List<CompletableFuture<Boolean>> futureValidations = List.of(stringValidator.codeValidator(code), stringValidator.systemValidator(system) ); CompletableFuture<Void> allFutureValidations = CompletableFuture.allOf( futureValidations.toArray(new CompletableFuture[futureValidations.size()])); CompletableFuture<List<Boolean>> validationResult = allFutureValidations.thenApply(v -> { return futureValidations.stream() .map(CompletableFuture::join) .collect(Collectors.toList()); }); CompletableFuture<Long> countFuture = validationResult.thenApply(allValidations -> { return allValidations.stream() .filter(Boolean::valueOf) .count(); }); try { if (countFuture.get() == futureValidations.size()){ try{ // 校验通过调用第三方接口 terminologyService.validateCode(system, code); // 原逻辑bug:校验通过应该返回true return true; }catch (ByCtsApiException e) { // 包装第三方受检异常后抛出 throw new CompletionException(e); } } return false; } catch (InterruptedException e) { // 中断异常需要先恢复中断标记再抛出 Thread.currentThread().interrupt(); throw new CompletionException(e); } catch (ExecutionException e) { // 拆出原始异常后包装抛出 Throwable realException = e.getCause() != null ? e.getCause() : e; throw new CompletionException(realException); } }, executor); } }
2. 适配ControllerAdvice的异常处理
控制器层调用countFuture.get()时会抛出ExecutionException,异常的cause是我们包装的CompletionException,再内层才是原始的业务异常(比如ByCtsApiException)。可以在全局异常处理中添加拆包逻辑,就能复用第三方库原有的异常处理逻辑:
@RestControllerAdvice public class GlobalExceptionHandler { // 处理异步场景的包装异常 @ExceptionHandler(ExecutionException.class) public Object handleExecutionException(ExecutionException e) throws Throwable { Throwable cause = e.getCause(); // 拆出CompletionException的内层异常 if (cause instanceof CompletionException) { cause = cause.getCause(); } // 抛出原始异常,交给对应的ExceptionHandler处理 throw cause; } // 你原有以及第三方库的其他ExceptionHandler无需修改 }
效果验证
修改后所有嵌套异步场景抛出的异常都会逐层向上传递到控制器层,最终被ControllerAdvice捕获,返回符合预期的响应结果。
内容的提问来源于stack exchange,提问作者Wrapper
相关产品推荐
相关产品推荐

