Java 8 CompletionStage 异常重新抛出异常

hac*_*ain 5 future promise playframework java-8 completable-future

exceptionally似乎不允许在 CompletionStage的方法内重新抛出异常。

我需要检查某种异常,如果没有,我需要重新抛出它:

Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);

CompletionStage<JsonNode> outcome = FutureConverters.toJava(futureSite);

return outcome.thenApplyAsync((siteJson) -> {
            Site site = Json.fromJson(siteJson, Site.class);
            try {
                return function.apply(site);
            } catch (RequestException e) {
                return e.result;
            }
        }, httpExecutionContext.current()).exceptionally(throwable -> {
            if(throwable instanceof SomeClientException) {
                if(((SomeClientException) throwable).httpStatusCode == 404) {
                   return entityNotFound("Site", siteId);
                }
            }


 // let JSON parsing failures and other errors bubble up, TODO play2.5


         throw throwable;
        });
Run Code Online (Sandbox Code Playgroud)

throw throwable 错误说 unhandledException java.lang.Throwable

什么接口可能允许重新抛出异常?或者有更好的方法吗?

更新 :

我尝试了以下 Holger 的建议,但我仍然不确定在什么时候我可以重新抛出这个:

BaseController.java : 
       protected class RequestException extends Exception {
            private static final long serialVersionUID = -2154871100650903869L;

            public Result result;

            public RequestException(Result result) {
                this.result = result;
            }
        }

        @FunctionalInterface
        protected interface RequestFunction<T, R> {
            R apply(T t) throws RequestException;
        }

    protected CompletionStage<Result> performWithSite(final Long siteId, RequestFunction<Site, Result> function) {
            QueryParams queryParams = QueryParams.create();
            Future<JsonNode> futureSite = someClient.getSite(siteId, queryParams);

            CompletionStage<JsonNode> outcome = FutureConverters.toJava(futureSite);

            return handleSpecific(
                    outcome.thenApplyAsync(siteJson -> {
                        Site site = Json.fromJson(siteJson, Site.class);
                        try {
                            return function.apply(site);
                        } catch (RequestException e) {
                            return e.result;
                        }
                    }, httpExecutionContext.current()),
                    throwable -> throwable instanceof SomeClientException
                            && ((SomeClientException)throwable).httpStatusCode == 404,
                    () -> entityNotFound("Site", siteId));
        } 

      protected Result entityNotFound(String entityName, String id) {
    // building our custom error model. 
  Error e = new Error(
                    Http.Status.NOT_FOUND,
                    ErrorCode.ENTITY_NOT_FOUND,
                    ErrorCodes.NOT_FOUND, new String[]{entityName, id});

            return notFound(e.asJson());
        }
Run Code Online (Sandbox Code Playgroud)

所以,上面代码的本质是我需要联系 someClient 来检查站点是否存在,并且客户端可以抛出 SomeClientException。

 Controller.java

       public Result destroy(Long siteId, Long productId){
            return performWithSite(siteId, site -> {
                productWriter.deleteProduct(siteId, productId);
                return noContent();
            }).toCompletableFuture().exceptionally(e -> {
              Logger.error(e+"exception");
               throw e.getCause(); 
              // above line errors out as unhandledException java.lang.throwable, I need the NotFoundException which is contained within the CompletionException to be thrown.
           });
        }
Run Code Online (Sandbox Code Playgroud)

// 在上面的控制器代码中,在我对客户端进行远程调用以检查站点是否存在后,我需要继续删除产品。

productWriter.deleteProduct(siteId, productId)可能仍然抛出一个NotFoundException或其他我需要重新抛出它的东西......这样从控制器代码重新抛出的任何异常都会在调用链中被我们的自定义异常处理程序接收。

这是我的测试用例:

  "return 404 when deleting a nonexistent Product" {
      when(productDAO.findBySiteAndProductId(anyLong(), anyLong())) thenReturn null

      a[NotFoundException] should be thrownBy { controller.destroy(0L, 1L) }
    }
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 4

Afaik,没有内置的解决方案。您可以传递给 的链接方法的所有函数类型都CompletionStage仅限于未经检查的异常。您可以构建自己的实用方法:

public static <T> CompletionStage<T> handleSpecific(
    CompletionStage<T> previousStage, Predicate<Throwable> p, Supplier<T> s) {

    CompletableFuture<T> result = new CompletableFuture<>();
    previousStage.whenComplete((value,throwable)->{
        if(throwable == null) result.complete(value);
        else {
            Throwable t = throwable;
            if(t instanceof CompletionException) {
                t = t.getCause();
                if(t == null) t = throwable;
            }
            if(p.test(t)) result.complete(s.get());
            else result.completeExceptionally(throwable);
        }
    });
    return result;
}
Run Code Online (Sandbox Code Playgroud)

该解决方案可以像这样使用:

return handleSpecific(
    outcome.thenApplyAsync(siteJson -> {
        Site site = Json.fromJson(siteJson, Site.class);
        try {
            return function.apply(site);
        } catch (RequestException e) {
            return e.result;
        }
    }, httpExecutionContext.current()),
    throwable -> throwable instanceof SomeClientException
              && ((SomeClientException)throwable).httpStatusCode == 404,
    () -> entityNotFound("Site", siteId));
Run Code Online (Sandbox Code Playgroud)

  • 对于那些使用 Java 12 的人来说,“CompletionStage#exceptionallyCompose”可能值得一看:https://docs.oracle.com/en/java/javase/12/docs/api/java.base/java/util/concurrent/CompletionStage .html#exceptionallyCompose(java.util.function.Function) (2认同)