Java 8 CompletionStage异常重新抛出异常

时间:2016-11-18 16:59:40

标签: playframework java-8 promise future completable-future

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

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

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;
        });

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());
        }

所以,上面代码的本质是我需要联系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.
           });
        }

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

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) }
    }

2 个答案:

答案 0 :(得分: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;
}

此解决方案可以像:

一样使用
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));

答案 1 :(得分:1)

为了从CompletionStage块中重新抛出一个未知异常,它应该被包装到CompletionException中,除非它已被包装。 CompletionException未经检查。在某些情况下,CompletionStage在内部执行相同的操作,但CompletionException专门用于避免双重换行,与其他未经检查的异常不同。

此外,在exceptionally块中,异常可能会显示为解包或包装一次,具体取决于链中先前块的数量,因此您可能需要检查链式异常。