如何正确处理来自 ListenableFuture guava 的异常?
How to properly deal with exceptions coming from ListenableFuture guava?
我有一个库,其中为我们的客户提供了同步和异步两种方法。他们可以调用他们认为适合他们目的的任何方法。
- executeSynchronous() - 等到我有结果,returns 结果。
- executeAsynchronous() - returns 如果需要,可以在完成其他事情后立即处理 Future。
他们将传递其中包含用户 ID 的 DataKey 对象。我们将根据用户 ID 确定要调用哪台机器。因此,我们将使用 AsyncRestTemplate 对 url 进行 http 调用,然后根据是否成功将响应发送回他们。
下面是我的界面:
public interface Client {
// for synchronous
public DataResponse executeSync(final DataKey key);
// for asynchronous
public Future<DataResponse> 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<DataResponse> 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<DataResponse> executeAsync(final DataKey keys) {
final SettableFuture<DataResponse> responseFuture = SettableFuture.create();
restTemplate.exchange(createURL(keys), HttpMethod.GET, keys.getEntity(), String.class).addCallback(
new ListenableFutureCallback<ResponseEntity<String>>() {
@Override
public void onSuccess(ResponseEntity<String> 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块中的异常? CancellationException 和 TimeoutException 有什么区别吗?另外,我们一般应该如何处理 ExecutionException
?
- 我的 DataKey 在我的界面中必须是最终的吗?如果我在我的 executeAsync 实现中删除最终变量,那么我会得到编译错误
Cannot refer to a non-final variable keys inside an inner class defined in a different method
.
- 这是在我的
executeAsync
方法中使用 ListenableFutureCallback 的正确方法吗?或者有更好的使用方法吗?
任何 inputs/suggestions 也欢迎在我的设计中使用同步和异步实现。
我假设您使用的是 Spring 4 (AsyncRestTemplate)。在这种情况下,您获得的 ListenableFuture 并不是真正的 Guava 的 ListenableFuture,而是 Spring 中的克隆。无论如何,您应该像处理标准 Future 中的异常一样处理异常。
您问题的答案:
// does this have to be final? private final AsyncRestTemplate
restTemplate = new AsyncRestTemplate();
它不是(在这种情况下),但这是一个很好的做法,因为通常它使对象不那么可变,从而简化了对其行为的推理。
catch (CancellationException e) {
// what to do here?
}
如果任务被取消(通过 Future#cancel 或 ExecutorService#shutdownNow),将抛出 CancellationException。在您的情况下不会发生这种情况,因为只有您引用了 Future 和(通过私有 AsyncRestTemplate 隐含地)执行查询使用的 ExecutorService。所以
throw new AssertionError("executeAsync task couldn't be cancelled", e);
Is there any difference between CancellationException and
TimeoutException?
在 Future#get 调用中您指定了超时。如果在 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 的代码在执行期间抛出异常。在您的情况下,永远不会抛出 ExecutionException,因为您 return SettableFuture 在 onSuccess 和 onFailure 回调中都设置了值,因此您可以在 catch 块中抛出 AssertionError。没有通用的ExecutionException响应策略。
Does my DataKey have to be final in my interface?
它在 executeAsync 实现中必须是最终的,因为您从匿名中引用它 class(onFailure 回调);
Is this the right way to use ListenableFutureCallback in my executeAsync method? Or is there any better way to use that?
没看出有什么问题。
一些建议:
- 考虑使异步客户端的线程池可配置。
默认情况下,AsyncRestTemplate 使用 SimpleAsyncTaskExecutor 为每个请求创建新线程。这可能不适合您的所有客户。请注意,如果您遵循此建议,对 CancellationException 的响应必须不同,因为客户端现在可以引用 ExecutorService:抛出 RuntimeException 应该没问题。
在(java)doc中描述默认使用的线程池!
我会拆分同步和异步版本。
我认为使用同步RestTemplate并通过同步版本实现异步版本会简化实现。
考虑 return 使用更灵活的 ListenableFuture 而不是普通的 Future(使用 SettableListenableFuture 而不是 SettableFuture)。
我有一个库,其中为我们的客户提供了同步和异步两种方法。他们可以调用他们认为适合他们目的的任何方法。
- executeSynchronous() - 等到我有结果,returns 结果。
- executeAsynchronous() - returns 如果需要,可以在完成其他事情后立即处理 Future。
他们将传递其中包含用户 ID 的 DataKey 对象。我们将根据用户 ID 确定要调用哪台机器。因此,我们将使用 AsyncRestTemplate 对 url 进行 http 调用,然后根据是否成功将响应发送回他们。
下面是我的界面:
public interface Client {
// for synchronous
public DataResponse executeSync(final DataKey key);
// for asynchronous
public Future<DataResponse> 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<DataResponse> 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<DataResponse> executeAsync(final DataKey keys) {
final SettableFuture<DataResponse> responseFuture = SettableFuture.create();
restTemplate.exchange(createURL(keys), HttpMethod.GET, keys.getEntity(), String.class).addCallback(
new ListenableFutureCallback<ResponseEntity<String>>() {
@Override
public void onSuccess(ResponseEntity<String> 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块中的异常? CancellationException 和 TimeoutException 有什么区别吗?另外,我们一般应该如何处理ExecutionException
? - 我的 DataKey 在我的界面中必须是最终的吗?如果我在我的 executeAsync 实现中删除最终变量,那么我会得到编译错误
Cannot refer to a non-final variable keys inside an inner class defined in a different method
. - 这是在我的
executeAsync
方法中使用 ListenableFutureCallback 的正确方法吗?或者有更好的使用方法吗?
任何 inputs/suggestions 也欢迎在我的设计中使用同步和异步实现。
我假设您使用的是 Spring 4 (AsyncRestTemplate)。在这种情况下,您获得的 ListenableFuture 并不是真正的 Guava 的 ListenableFuture,而是 Spring 中的克隆。无论如何,您应该像处理标准 Future 中的异常一样处理异常。
您问题的答案:
// does this have to be final? private final AsyncRestTemplate restTemplate = new AsyncRestTemplate();
它不是(在这种情况下),但这是一个很好的做法,因为通常它使对象不那么可变,从而简化了对其行为的推理。
catch (CancellationException e) { // what to do here? }
如果任务被取消(通过 Future#cancel 或 ExecutorService#shutdownNow),将抛出 CancellationException。在您的情况下不会发生这种情况,因为只有您引用了 Future 和(通过私有 AsyncRestTemplate 隐含地)执行查询使用的 ExecutorService。所以
throw new AssertionError("executeAsync task couldn't be cancelled", e);
Is there any difference between CancellationException and TimeoutException?
在 Future#get 调用中您指定了超时。如果在 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 的代码在执行期间抛出异常。在您的情况下,永远不会抛出 ExecutionException,因为您 return SettableFuture 在 onSuccess 和 onFailure 回调中都设置了值,因此您可以在 catch 块中抛出 AssertionError。没有通用的ExecutionException响应策略。
Does my DataKey have to be final in my interface?
它在 executeAsync 实现中必须是最终的,因为您从匿名中引用它 class(onFailure 回调);
Is this the right way to use ListenableFutureCallback in my executeAsync method? Or is there any better way to use that?
没看出有什么问题。
一些建议:
- 考虑使异步客户端的线程池可配置。
默认情况下,AsyncRestTemplate 使用 SimpleAsyncTaskExecutor 为每个请求创建新线程。这可能不适合您的所有客户。请注意,如果您遵循此建议,对 CancellationException 的响应必须不同,因为客户端现在可以引用 ExecutorService:抛出 RuntimeException 应该没问题。
在(java)doc中描述默认使用的线程池!
我会拆分同步和异步版本。
我认为使用同步RestTemplate并通过同步版本实现异步版本会简化实现。
考虑 return 使用更灵活的 ListenableFuture 而不是普通的 Future(使用 SettableListenableFuture 而不是 SettableFuture)。