web-dev-qa-db-ja.com

AsyncRestTemplateを使用してAPIを複数回作成し、すべてが完了するのを待ちます

異なるパラメーターでRestTemplateを複数回使用してRestAPI呼び出しを行う必要があります。 APIは同じですが、変更されるのはパラメーターです。回数も可変です。 AsyncRestTemplateを使用したいのですが、メインスレッドはすべてのAPI呼び出しが正常に完了するまで待機する必要があります。また、各API呼び出しが返した応答を処理したいと思います。現在、RestTemplateを使用しています。基本形は以下の通りです。

List<String> listOfResponses = new ArrayList<String>();
for (Integer studentId : studentIdsList) {
    String respBody;
    try {
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, method, requestEntity, String.class);
    } catch (Exception ex) {
        throw new ApplicationException("Exception while making Rest call.", ex);
    }
    respBody = requestEntity.getBody();
    listOfResponses.add(respBody);          
}

この状況でAsyncRestTemplateを実装するにはどうすればよいですか?

7
TV Nath

AsyncRestTemplate(または実際には非同期API)を使用する場合の主なアイデアは、すべてのリクエストを最初に送信し、対応する先物を保持してから、すべての応答を2回目に処理することです。これは、2つのループで簡単に実行できます。

List<ListenableFuture<ResponseEntity<String>>> responseFutures = new ArrayList<>();
for (Integer studentId : studentIdsList) {
    // FIXME studentId is not used
    ListenableFuture<ResponseEntity<String>> responseEntityFuture = restTemplate.exchange(url, method, requestEntity, String.class);
    responseFutures.add(responseEntityFuture);
}
// now all requests were send, so we can process the responses
List<String> listOfResponses = new ArrayList<>();
for (ListenableFuture<ResponseEntity<String>> future: responseFutures) {
    try {
        String respBody = future.get().getBody();
        listOfResponses.add(respBody);
    } catch (Exception ex) {
        throw new ApplicationException("Exception while making Rest call.", ex);
    }
}

注:応答を元の要求とペアにする必要がある場合は、先物のリストをマップまたは要求+応答オブジェクトのリストに置き換えることができます。

また、あなたの質問ではstudentIdが使用されていないことにも注意しました。

12
Didier L

可能であれば、Java 8 Stream APIを使用できます:

List<String> listOfResponses = studentIdsList.stream()
    .parrallel()
    .map({studentId ->
        ResponseEntity<String> responseEntity = restTemplate.exchange(url, method, studentId, String.class);
        return responseEntity.getBody();
    })
    .collect(Collectors.toList());

このコードは基本的に2つのことを実行します:

  1. リクエストを並行して実行します。
  2. リクエストの結果をリストに収集します。

更新:@Didier Lに同意します-多くのリクエストを行う必要がある場合、このソリューションは正しく機能しない可能性があります。更新されたバージョンは次のとおりです。

List<String> listOfResponses  = studentIdsList.stream()
                .map(studentId -> asyncRestTemplate.exchange(url, method, studentId, String.class)
                .collect(Collectors.toList()).stream()
                .map(this::retrieveResult)
                .collect(Collectors.toList());

    /**
     * Retrieves results of each request by blocking the main thread. Note that the actual request was performed on the previous step when
     * calling asyncRestTemplate.exchange(url, method, studentId, String.class)
     */
    private String retrieveResult(ListenableFuture<ResponseEntity<String>> listenableFuture) {
        try {
            return listenableFuture.get().getBody();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
4
Danylo Zatorsky

AsyncRestTemplateではなくSpringのRestTemplateを使用する別の解決策を提案したいと思います。また、Java 8CompletableFutureを使用しています。

public void sendRequestsAsync(List<Integer> studentList) {
    List<CompletableFuture<Void>> completableFutures = new ArrayList<>(studentList.size()); //List to hold all the completable futures
    List<String> responses = new ArrayList<>(); //List for responses
    ExecutorService yourOwnExecutor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

    for (Integer studentId : studentList) { //Iterate student list
        CompletableFuture<Void> requestCompletableFuture = CompletableFuture
                .supplyAsync(
                        () -> restTemplate.exchange("URL/" + studentId, HttpMethod.GET, null, String.class),
                        yourOwnExecutor
                )//Supply the task you wanna run, in your case http request
                .thenApply((responseEntity) -> {
                    responses.add(responseEntity.getBody());
                    return responseEntity;
                })//now you can add response body to responses
                .thenAccept((responseEntity) -> {
                    doSomeFinalStuffWithResponse(responseEntity);
                })//here you can do more stuff with responseEntity (if you need to)
                .exceptionally(ex -> {
                    System.out.println(ex);
                    return null;
                });//do something here if an exception occurs in the execution;

        completableFutures.add(requestCompletableFuture);
    }

    try {
        CompletableFuture.allOf(completableFutures.toArray(new CompletableFuture[completableFutures.size()])).get(); //Now block till all of them are executed by building another completablefuture with others.
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}

私はこのソリューションがもっと好きです。なぜなら、必要なだけビジネスロジックをチェーンでき、非同期送信のためにSpringの内部に依存する必要がないからです。明らかに、コードをさらにクリーンアップすることができますが、今のところあまり注意を払っていません。

1
Sneh