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

嵌套调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 09:36:02