【问题标题】:Throw actual exception from completeablefuture从 completablefuture 抛出实际异常
【发布时间】:2021-06-17 17:31:29
【问题描述】:

我正在使用 completablefuture 进行并行调用,如下所示,

public Response getResponse() {
    Response resultClass = new Response();
   try {
    CompletableFuture<Optional<ClassA>> classAFuture
        = CompletableFuture.supplyAsync(() -> service.getClassA() );
    CompletableFuture<ClassB> classBFuture
        = CompletableFuture.supplyAsync(() -> {
             try {
                   return service.getClassB(); 
              }
              catch (Exception e) {
                   throw new CompletionException(e);
              }
     });

   CompletableFuture<Response> responseFuture =
    CompletableFuture.allOf(classAFuture, classBFuture)
         .thenApplyAsync(dummy -> {
            if (classAFuture.join().isPresent() {
               ClassA classA = classAFuture.join();
               classA.setClassB(classBFuture.join());
               response.setClassA(classA)
             }
            return response;
         });
   responseFuture.join();
  } catch (CompletionExecution e) {
    throw e;
  }
  return response;
}

我需要为return service.getClassB() 添加try catch,因为它会在getClassB 方法中引发异常。

现在我面临的问题是如果service.getClassB() 抛出错误,它总是包裹在java.util.concurrent.ExecutionException 中。在这种情况下,此方法会抛出 UserNameNotFoundException ,但它被包裹在 ExecutionException 中,并且它没有被捕获在正确的位置 @ControllerAdvice 异常处理程序类中。我尝试了使用 throwable 的不同选项,但没有帮助。

有没有很好的方法来处理异常并解包并将其发送到@ControllerAdvice 类?

【问题讨论】:

  • 你不能为ExecutionException 创建一个@ControllerAdvice 并在其中检查它的原因吗?通过Exception::getCause 并将通常的UserNameNotFoundExceptionExecutionException 控制器建议委托给相同的通用逻辑?
  • catch (CompletionExecution e) { throw e; } 的意义何在?再接再扔的效果,就像当初没接住一样。所以你最终会抛出一个CompletionExecution,而不是ExecutionException。为什么操作的结果是一个不在范围内的变量(response)?应该是resultClass,你在方法开始时声明和初始化的变量?
  • (注意:我在这里投了反对票,因为在这里写代码时引入了几个拼写错误。代码应该总是粘贴,以免犯这些可避免的错误。我对初学者很容易在这里,但不是 28K 用户)。

标签: spring-boot java-8 completable-future


【解决方案1】:

您的代码有几个错误,例如引用了未在此代码中声明的变量response,并且很可能应该是开头声明的resultClass。这行
ClassA classA = classAFuture.join(); 突然忽略了这个未来封装了一个Optional,并且缺少); 分隔符。

此外,当有干净的替代方案时,您应该避免从周围的代码中访问变量。此外,使用allOf 组合两个期货是不必要的复杂化。

如果我正确理解你的意图,你想做类似的事情

public Response getResponse() {
    return CompletableFuture.supplyAsync(() -> service.getClassA())
        .thenCombine(CompletableFuture.supplyAsync(() -> {
            try {
                return service.getClassB();
            }
            catch(ExecutionException e) {
                throw new CompletionException(e.getCause());
            }
        }), (optA, b) -> {
            Response response = new Response();
            optA.ifPresent(a -> {
                a.setClassB(b);
                response.setClassA(a);
            });
            return response;
        })
        .join();
}

解决您描述的问题的关键是捕获最具体的异常类型。当你捕捉到ExecutionException 时,你就知道它会包装实际的异常并且可以无条件地提取它。当getClassB() 声明您必须捕获的其他检查异常时,添加另一个catch 子句,但要具体而不是捕获Exception,例如

try {
    return service.getClassB();
}
catch(ExecutionException e) {
    throw new CompletionException(e.getCause());
}
catch(IOException | SQLException e) { // all of getClassB()’s declared exceptions
    throw new CompletionException(e); //     except ExecutionException, of course
}

【讨论】:

  • 但这会引发CompletionException? OP 正在寻找扔掉 e.getCause 中的任何内容,以便触发他/她的 @ControllerAdvice
  • @Eugene OP 只谈到了一个无意的ExecutionException。除此之外,答案还显示了如何捕获此类异常并提取原因,因此对其他包装异常类型的任何适应都应该是轻而易举的事。
  • 你是对的,但不费吹灰之力的部分也是在春季稍微重构最初的建议。我认为稍微修改一下是合适的,恕我直言。不过,我完全明白你的意思。
猜你喜欢
  • 2017-11-08
  • 2018-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多