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

  • executeSynchronous()-等待直到得到结果,然后返回结果。
  • executeAsynchronous()-立即返回一个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的正确方法吗?还是有更好的方法来使用它?

  • 在我的设计中,对于具有同步和异步实现的任何输入/建议也都受到欢迎。

    最佳答案

    我假设您正在使用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);
    

    CancellationException和
    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,因为您返回的settableFuture在onSuccess和onFailure回调中均设置了值,因此可以在catch块中引发AssertionError。没有响应ExecutionException的通用策略。

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

    它必须在executeAsync实现中是最终的,因为您是从匿名类(onFailure回调)中引用它的;

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

    没有发现任何问题。

    一些建议:
  • 考虑使异步客户端的线程池可配置。

  • 默认情况下,AsyncRestTemplate使用SimpleAsyncTaskExecutor为每个请求创建新线程。这可能并不适合所有客户。请注意,如果您遵循此建议,对CancellationException的响应必须有所不同,因为客户端现在可以引用ExecutorService:抛出RuntimeException应该很好。
  • 描述默认使用的(java)doc线程池!
  • 我会拆分同步和异步版本。
  • 我认为使用同步RestTemplate并通过同步版本实现异步版本将简化实现。
  • 考虑返回更灵活的ListenableFuture而不是普通的Future(使用SettableListenableFuture代替SettableFuture)。
  • 07-24 21:33