当CompleteableFuture抛出exception时返回500内部错误

我有一个具有端点的rest控制器:

@GET @Path("/reindex-record") public String reindexRecord(@QueryParam("id") String id) { if (StringUtils.isEmpty(id)) { CompletableFuture.runAsync( () -> runWithException(Reindexer::reindexAll)); } else { CompletableFuture.runAsync(() -> runWithException( () -> Reindexer.reindexOne(id))); } // return "ok" or throw WebApplciationException from runWithException method below } 

这里是我的包装器方法 – 两个方法 – reindexAll和reindexOne抛出已检查的exception,因此决定使用包装器方法和接口:

 public interface RunnableWithException { void run() throws Exception; } private void runWithException(RunnableWithException task) { try { task.run(); } catch (Exception e) { log.error("Error occured during async task execution", e); throw new WebApplicationException( Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Internal error occurred").build()); } } 

问题是我想使用CompleteableFuture异步运行此任务,并且仅在给定任务完成后给出响应,或者如果有错误则抛出具有INTERNAL_SERVER_ERROR状态的WebApplicationException。

你如何在我的用例if / else中实现它?

编辑:截至目前我有这个方法:

 @GET @Path("/reindex-record") public String reindexRecord(@QueryParam("id") String id) throws ExecutionException, InterruptedException { CompletableFuture task; if (StringUtils.isEmpty(id)) { task = CompletableFuture.runAsync( () -> runWithException(Reindexer::reindexAll)); } else { task = CompletableFuture.runAsync(() -> runWithException( () -> Reindexer.reindexOne(id))); } return task.thenApply(x -> "ok") .exceptionally(throwable -> { log.error("Error occured during async task execution", throwable); throw new WebApplicationException(Response.status(Response.Status.SERVICE_UNAVAILABLE) .entity("Internal error occurred. Try again later") .build()); }).get(); } 

但是如果任何Reindexer方法抛出错误,我仍然会获得数据的状态500:

 { "code": 500, "message": "There was an error processing your request. It has been logged (ID 03f09a62b62b1649)." 

}

而不是在我的exceptionally块中定义的503。 如果重要的话,使用带有JAX-RS的dropwizard。

您可以将方法的主体更改为:

 @GET @Path("/reindex-record") public String reindexRecord(@QueryParam("id") String id) { final CompletableFuture future; if (StringUtils.isEmpty(id)) { future = CompletableFuture.runAsync( () -> runWithException(Reindexer::reindexAll)); } else { future = CompletableFuture.runAsync( () -> runWithException(() -> Reindexer.reindexOne(id))); } // this will block future.get(); return "ok"; } 

通过存储未来,您可以在其上调用get()方法,该方法将阻塞直到将来完成。

CompletableFuture.get()的javadoc:

如果有必要等待此未来完成,然后返回其结果。

问题是你正在使用exceptionally()来处理它不想要的东西。 CompletableFuture旨在用于CompletableFuture链中,其中一个输出将输入到下一个。 如果其中一个CompletableFuture抛出exception会发生什么? 您可以exceptionally地使用它来捕获它并返回一个新的后备ComletableFuture以便链的下一步使用。

你没有这样做,你只是抛出一个WebApplicationException 。 CompleteableFuture将链中的失败视为一个失败,并将您的WebApplicationException包装在ExecutionException 。 Dropwizard只能看到ExecutionException(它不检查任何包装的)并抛出generics500响应。

答案就是做future.get(); 在@Lino的答案中,但是包含在ExecutionException的try … catch块中,然后从catch中抛出WebApplicationException。

 try { // this will block future.get(); } catch (ExecutioException) { throw new WebApplicationException( Response.status(Response.Status.INTERNAL_SERVER_ERROR) .entity("Internal error occurred").build()); } return "ok"; 

您可能能够缩短整个throw new WebApplicationException(...以简单地throw new InternalServerErrorException()