如何正确处理来自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的正确方法吗? 或者有更好的方法来使用它吗?
看不出有任何问题。
一些建议:
- 考虑为异步客户端配置线程池。
默认情况下,AsyncRestTemplate使用SimpleAsyncTaskExecutor为每个请求创建新线程。 这可能不适合所有客户。 请注意,如果您遵循此建议,则对CancellationException的响应必须不同,因为客户端现在可以引用ExecutorService:抛出RuntimeException应该没问题。
-
在(java)doc线程池中描述默认使用!
-
我会拆分同步和异步版本。
-
我认为使用同步RestTemplate并通过同步版本实现异步版本将简化实现。
-
考虑返回更灵活的ListenableFuture而不是简单的Future(使用SettableListenableFuture而不是SettableFuture)。