Ответ 1
Я предполагаю, что вы используете 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? }
CancellationException будет выведено, если задача отменена (либо через Future # cancel, либо ExecutorService # shutdownNow). Это не может произойти в вашем случае, поскольку только у вас есть ссылки на Future и (неявно через приватную AsyncRestTemplate) ExecutorService, используемую для выполнения запросов. Итак,
throw new AssertionError("executeAsync task couldn't be cancelled", e);
Есть ли разница между CancellationException и TimeoutException?
В Future # get call вы указали время ожидания. TimeoutException будет выведено, если результат по-прежнему недоступен после keys.getTimeout() миллисекунд.
catch (InterruptedException e) { // is this right way to deal with InterruptedException? throw new RuntimeException("Interrupted", e); }
В этом случае нет. InterruptedException будет выбрано, когда поток клиента будет прерван. Вы не владеете этим потоком, поэтому вам следует распространять InterruptedException (т.е. Объявлять executeSync(DataKey keys) throws InterruptedException
). Если по какой-либо причине вы не можете изменить подпись метода, то, по крайней мере, восстановить прерванный флаг (Thread.currentThread().interrupt()
), прежде чем бросать RuntimeException.
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 означает, что код, представленный ExecutorService как Callable/Runnable, вызывал исключение во время выполнения. В вашем случае ExecutionException никогда не будет выбрано, потому что вы возвращаете SettableFuture со значением, установленным как в обратных вызовах onSuccess, так и onFailure, поэтому вы можете бросить AssertionError в блоке catch. Нет общей стратегии ответа на ExecutionException.
Должен ли мой DataKey быть окончательным в моем интерфейсе?
Он должен быть окончательным в реализации executeAsync, потому что вы ссылаетесь на него с анонимного класса (обратный вызов onFailure);
Является ли это правильным способом использования ListenableFutureCallback в моем методе executeAsync? Или есть лучший способ использовать это?
Не вижу в этом ничего плохого.
Некоторые советы:
- Рассмотрите возможность создания пула потоков для асинхронного клиента.
По умолчанию AsyncRestTemplate использует SimpleAsyncTaskExecutor, который создает новый поток для каждого запроса. Это может быть неприемлемо для всех ваших клиентов. Обратите внимание, что если вы следуете этому совету, ответ на CancellationException должен быть другим, поскольку клиент теперь может иметь ссылку на ExecutorService: бросать RuntimeException должно быть хорошо.
-
Описать в (java) пуле потоков doc, используемом по умолчанию!
-
Я бы разделил версии синхронизации и асинхронизации.
-
Я думаю, что использование sync RestTemplate и реализация async-версии с помощью версии синхронизации упростило бы реализацию.
-
Рассмотрите возможность возврата более гибкого ListenableFuture вместо простого будущего (используя SettableListenableFuture вместо SettableFuture).