web-dev-qa-db-fra.com

Comment utiliser AsyncRestTemplate pour passer plusieurs appels simultanément?

Je ne comprends pas comment utiliser AsyncRestTemplate efficacement pour effectuer des appels de service externes. Pour le code ci-dessous:

class Foo {

    public void doStuff() {
        Future<ResponseEntity<String>> future1 = asyncRestTemplate.getForEntity(
                url1, String.class);
        String response1 = future1.get();

        Future<ResponseEntity<String>> future2 = asyncRestTemplate.getForEntity(
                url2, String.class);
        String response2 = future2.get();

        Future<ResponseEntity<String>> future3 = asyncRestTemplate.getForEntity(
                url3, String.class);
        String response3 = future3.get();
    }
}

Idéalement, je veux exécuter les 3 appels simultanément et traiter les résultats une fois qu'ils sont tous terminés. Cependant chaque appel de service externe est pas récupéré jusqu'à l'appel de get() mais get() est bloqué. Cela ne va-t-il donc pas à l'encontre du but de AsyncRestTemplate? Je pourrais aussi bien utiliser RestTemplate.

Je ne comprends donc pas comment les exécuter simultanément?

16
Glide

N'appelez tout simplement pas le blocage get() avant d'envoyer tous vos appels asynchrones:

class Foo {
  public void doStuff() {
    ListenableFuture<ResponseEntity<String>> future1 = asyncRestTemplate
        .getForEntity(url1, String.class);
    ListenableFuture<ResponseEntity<String>> future2 = asyncRestTemplate
        .getForEntity(url2, String.class);
    ListenableFuture<ResponseEntity<String>> future3 = asyncRestTemplate
        .getForEntity(url3, String.class);

    String response1 = future1.get();
    String response2 = future2.get();
    String response3 = future3.get();
  }
}

Vous pouvez faire à la fois dispatch et get en boucles, mais notez que la collecte des résultats actuels est inefficace car elle resterait bloquée sur le prochain avenir inachevé.

Vous pouvez ajouter tous les futurs à une collection, et itérer à travers elle en testant chaque futur pour ne pas bloquer isDone(). Lorsque cet appel renvoie true, vous pouvez alors appeler get().

De cette façon, votre collecte de résultats en masse sera optimisée plutôt que d'attendre le prochain résultat futur lent dans l'ordre d'appeler get() s.

Mieux encore, vous pouvez enregistrer des rappels (runtimes) dans chaque ListenableFuture retourné par AccyncRestTemplate et vous n'avez pas à vous soucier de l'inspection cyclique des résultats potentiels.

15
diginoise

Si vous n'avez pas à utiliser 'AsyncRestTemplate', je suggère d'utiliser RxJava à la place. L'opérateur RxJava Zip est ce que vous recherchez. Vérifiez le code ci-dessous:

private rx.Observable<String> externalCall(String url, int delayMilliseconds) {
    return rx.Observable.create(
            subscriber -> {
                try {
                    Thread.sleep(delayMilliseconds); //simulate long operation
                    subscriber.onNext("response(" + url + ") ");
                    subscriber.onCompleted();
                } catch (InterruptedException e) {
                    subscriber.onError(e);
                }
            }
    );
}

public void callServices() {
    rx.Observable<String> call1 = externalCall("url1", 1000).subscribeOn(Schedulers.newThread());
    rx.Observable<String> call2 = externalCall("url2", 4000).subscribeOn(Schedulers.newThread());
    rx.Observable<String> call3 = externalCall("url3", 5000).subscribeOn(Schedulers.newThread());
    rx.Observable.Zip(call1, call2, call3, (resp1, resp2, resp3) -> resp1 + resp2 + resp3)
            .subscribeOn(Schedulers.newThread())
            .subscribe(response -> System.out.println("done with: " + response));
}

Toutes les demandes à des services externes seront exécutées dans des threads séparés, lorsque le dernier appel sera terminé, la fonction de transformation (par exemple la concaténation de chaîne simple) sera appliquée et le résultat (chaîne concaténée) sera émis à partir de `` Zip '' observable.

6
lkz

Ce que je comprends par votre question est que vous avez une méthode asynchrone prédéfinie et que vous essayez de faire est d'appeler cette méthode de manière asynchrone à l'aide de la classe RestTemplate.

J'ai écrit une méthode qui vous aidera à appeler votre méthode de manière asynchrone.

 public void testMyAsynchronousMethod(String... args) throws Exception {
        // Start the clock
        long start = System.currentTimeMillis();

        // Kick of multiple, asynchronous lookups
        Future<String> future1 = asyncRestTemplate
        .getForEntity(url1, String.class);;
        Future<String> future2 = asyncRestTemplate
        .getForEntity(url2, String.class);
        Future<String> future3 = asyncRestTemplate
        .getForEntity(url3, String.class);

        // Wait until they are all done
        while (!(future1 .isDone() && future2.isDone() && future3.isDone())) {
            Thread.sleep(10); //10-millisecond pause between each check
        }

        // Print results, including elapsed time
        System.out.println("Elapsed time: " + (System.currentTimeMillis() - start));
        System.out.println(future1.get());
        System.out.println(future2.get());
        System.out.println(future3.get());
    }
6
Vikrant Kashyap

Vous pouvez utiliser la classe CompletableFuture ( javadoc ).

  1. Transformez vos appels en CompletableFuture. Par exemple.

    final CompletableFuture<ResponseEntity<String>> cf = CompletableFuture.supplyAsync(() -> {
        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            throw new RuntimeException(e);
        }
    });
    
  2. Appelez ensuite la méthode CompletableFuture::allOf Avec vos 3 futurs complétables nouvellement créés.

  3. Appelez la méthode join() sur le résultat. Une fois le futur complétable résultant résolu, vous pouvez obtenir les résultats de chaque futur complétable distinct que vous avez créé à l'étape 3.

1
Kuvaldis