如何正确处理来自ListenableFuture番石榴的exception?

我有一个库,我已经为我们的客户提供了两种方法,sync和async。 他们可以调用他们认为适合他们目的的任何方法。

  • executeSynchronous() – 等到我有结果,返回结果。
  • executeAsynchronous() – 立即返回一个Future,如果需要,可以在其他事情完成后处理。

它们将传递具有用户标识的DataKey对象。 我们将根据用户ID确定调用哪台机器。 因此,我们将使用AsyncRestTemplate对URL进行http调用,然后根据它是否成功将响应发送给它们。

以下是我的界面:

public interface Client { // for synchronous public DataResponse executeSync(final DataKey key); // for asynchronous public Future executeAsync(final DataKey key); } 

以下是我的实施:

 public class DataClient implements IClient { // does this have to be final? private final AsyncRestTemplate restTemplate = new AsyncRestTemplate(); @Override public DataResponse executeSync(final DataKey keys) { Future responseFuture = executeAsync(keys); DataResponse response = null; try { response = responseFuture.get(keys.getTimeout(), TimeUnit.Milliseconds); } catch (CancellationException e) { // what to do here? } catch (InterruptedException e) { // is this right way to deal with InterruptedException? throw new RuntimeException("Interrupted", e); } catch (ExecutionException e) { // what do you mean by ExecutionException? And how should we deal with this? DataLogging.logErrors(e.getCause(), DataErrorEnum.ERROR_CLIENT, keys); response = new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR); } catch (TimeoutException e) { DataLogging.logErrors(e.getCause(), DataErrorEnum.TIMEOUT_ON_CLIENT, keys); response = new DataResponse(null, DataErrorEnum.TIMEOUT_ON_CLIENT, DataStatusEnum.ERROR); } return response; } @Override public Future executeAsync(final DataKey keys) { final SettableFuture responseFuture = SettableFuture.create(); restTemplate.exchange(createURL(keys), HttpMethod.GET, keys.getEntity(), String.class).addCallback( new ListenableFutureCallback<ResponseEntity>() { @Override public void onSuccess(ResponseEntity result) { responseFuture.set(new DataResponse(result.getBody(), DataErrorEnum.OK, DataStatusEnum.SUCCESS)); } @Override public void onFailure(Throwable ex) { DataLogging.logErrors(ex, DataErrorEnum.ERROR_SERVER, keys); responseFuture.set(new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR)); } }); return responseFuture; } } 

现在我的问题是:

  • 如何正确处理executeSync的catch块中的exception? CancellationException和TimeoutException之间有什么区别吗? 还有我们应该如何处理ExecutionException
  • 我的DataKey必须在我的界面中是最终的吗? 如果我在executeAsync实现中删除final变量,那么我得到编译错误,因为Cannot refer to a non-final variable keys inside an inner class defined in a different method
  • 这是在executeAsync方法中使用ListenableFutureCallback的正确方法吗? 或者有更好的方法来使用它吗?

我的设计也欢迎任何输入/建议,以实现同步和异步实现。

我假设您正在使用Spring 4( AsyncRestTemplate )。 在这种情况下,你得到的ListenableFuture不是真正的Guava的ListenableFuture,但它是Spring中的克隆。 无论如何,您应该像处理标准Future中的exception一样处理exception。

您的问题的答案:

 // does this have to be final? private final AsyncRestTemplate restTemplate = new AsyncRestTemplate(); 

它不(在这种情况下),但这是一个很好的做法,因为一般来说它使对象不那么可变,简化了对它的行为的推理。

 catch (CancellationException e) { // what to do here? } 

如果取消任务(通过Future#canceling或ExecutorService #shutdownNow),将抛出CancellationException。 在您的情况下不会发生这种情况,因为只有您具有对Future的引用和(隐式地通过私有AsyncRestTemplate)执行查询使用的ExecutorService。 所以

 throw new AssertionError("executeAsync task couldn't be cancelled", e); 

CancellationException和TimeoutException之间有什么区别吗?

在未来#get call中你已经指定了超时。 如果在keys.getTimeout()毫秒之后结果仍然不可用,则抛出TimeoutException。

 catch (InterruptedException e) { // is this right way to deal with InterruptedException? throw new RuntimeException("Interrupted", e); } 

在这种情况下没有。 当客户端的线程被中断时,将抛出InterruptedException。 你没有拥有该线程所以你应该传播InterruptedException(即声明executeSync(DataKey keys) throws InterruptedException )。 如果由于某种原因你无法更改方法的签名,那么至少在抛出RuntimeException之前恢复中断标志( Thread.currentThread().interrupt() )。

 catch (ExecutionException e) { // what do you mean by ExecutionException? And how should we deal with this? DataLogging.logErrors(e.getCause(), DataErrorEnum.ERROR_CLIENT, keys); response = new DataResponse(null, DataErrorEnum.ERROR_CLIENT, DataStatusEnum.ERROR); } 

ExecutionException意味着作为Callable / Runnable提交给ExecutorService的代码在执行期间引发了exception。 在你的情况下,永远不会抛出ExecutionException,因为你返回的SettableFuture在onSuccess和onFailure回调中都设置了值,所以你可以在catch块中抛出AssertionError。 没有响应ExecutionException的一般策略。

我的DataKey必须在我的界面中是最终的吗?

它必须是executeAsync实现中的final,因为你从匿名类引用它(onFailure callback);

这是在executeAsync方法中使用ListenableFutureCallback的正确方法吗? 或者有更好的方法来使用它吗?

看不出有任何问题。

一些建议:

  1. 考虑为异步客户端配置线程池。

默认情况下,AsyncRestTemplate使用SimpleAsyncTaskExecutor为每个请求创建新线程。 这可能不适合所有客户。 请注意,如果您遵循此建议,则对CancellationException的响应必须不同,因为客户端现在可以引用ExecutorService:抛出RuntimeException应该没问题。

  1. 在(java)doc线程池中描述默认使用!

  2. 我会拆分同步和异步版本。

  3. 我认为使用同步RestTemplate并通过同步版本实现异步版本将简化实现。

  4. 考虑返回更灵活的ListenableFuture而不是简单的Future(使用SettableListenableFuture而不是SettableFuture)。