web-dev-qa-db-fra.com

Différence entre @Async, DeferredResult et Callable de Spring MVC

J'ai une tâche de longue durée définie dans un service Spring. Il est démarré par un contrôleur Spring MVC. Je veux démarrer le service et renvoyer un HttpResponse à l'appelant avant la fin du service. Le service enregistre un fichier sur le système de fichiers à la fin. En javascript, j'ai créé un travail d'interrogation pour vérifier l'état du service.

Au printemps 3.2, j'ai trouvé le @Async annotation, mais je ne comprends pas en quoi elle est différente de DeferredResult et Callable. Quand dois-je utiliser @Async et quand dois-je utiliser DeferredResult?

26
Premier

Async annote une méthode afin qu'elle soit appelée de manière asynchrone.

@org.springframework.stereotype.Service
public class MyService {
    @org.springframework.scheduling.annotation.Async
    void DoSomeWork(String url) {
        [...]
    }
}

Donc, Spring pourrait le faire, vous devez donc définir comment cela va être exécuté. Par exemple:

<task:annotation-driven />
<task:executor id="executor" pool-size="5-10" queue-capacity="100"/>

De cette façon, lorsque vous appelez service.DoSomeWork ("paramètre"), l'appel est placé dans la file d'attente de l'exécuteur pour être appelé de manière asynchrone. Ceci est utile pour les tâches pouvant être exécutées simultanément.

Vous pouvez utiliser Async pour exécuter tout type de tâche asynchrone. Si ce que vous voulez appeler une tâche périodiquement, vous pouvez utiliser @ Scheduled (et utiliser task: scheduler au lieu de task: executor). Ce sont des moyens simplifiés d'appeler Java Runnables.

DeferredResult <> est utilisé pour répondre à une pétition sans bloquer le thread HTTP Tomcat utilisé pour répondre. Va généralement être la valeur de retour pour une méthode annotée ResponseBody.

@org.springframework.stereotype.Controller
{
    private final Java.util.concurrent.LinkedBlockingQueue<DeferredResult<String>> suspendedRequests = new Java.util.concurrent.LinkedBlockingQueue<>();

    @RequestMapping(value = "/getValue")
    @ResponseBody
    DeferredResult<String> getValue() {
            final DeferredResult<String> result = new DeferredResult<>(null, null);
            this.suspendedRequests.add(result);
            result.onCompletion(new Runnable() {
            @Override
            public void run() {
        suspendedRequests.remove(result);
            }
});
            service.setValue(result); // Sets the value!
            return result;
    }
}

L'exemple précédent manque d'une chose importante et c'est qu'il ne montre pas comment le résultat différé va être défini. Dans une autre méthode (probablement la méthode setValue), il y aura un result.setResult (valeur). Après l'appel à setResult Spring va appeler la procédure onCompletion et retourner la réponse à la requête HTTP (voir https://en.wikipedia.org/wiki/Push_technology#Long_polling ).

Mais si vous exécutez simplement setValue de manière synchrone, il n'y a aucun avantage à utiliser un résultat différé. C'est là que Async entre en jeu. Vous pouvez utiliser une méthode asynchrone pour définir la valeur de retour à un moment donné à l'aide d'un autre thread.

    @org.springframework.scheduling.annotation.Async
    void SetValue(DeferredResult<String> result) {
        String value;
        // Do some time consuming actions
        [...]
        result.setResult(value);
    }

Async n'est pas nécessaire pour utiliser un résultat différé, c'est juste une façon de le faire.

Dans l'exemple, il existe une file d'attente de résultats différés que, par exemple, une tâche planifiée peut surveiller pour traiter ses demandes en attente. Vous pouvez également utiliser un mécanisme non bloquant (voir http://en.wikipedia.org/wiki/New_I/O ) pour définir la valeur de retour.

Pour compléter l'image, vous pouvez rechercher des informations sur Java futures standard ( http://docs.Oracle.com/javase/1.5.0/docs/api/Java/util/ concurrent/Future.html ) et callables ( http://docs.Oracle.com/javase/1.5.0/docs/api/Java/util/concurrent/Callable.html ) qui sont quelque peu équivalents à Spring DeferredResult et Async.

21
Josep Panadero

Votre contrôleur est finalement une fonction exécutée par le thread de travail du conteneur de servlet (je suppose que c'est Tomcat). Votre flux de services commence avec Tomcat et se termine avec Tomcat. Tomcat obtient la demande du client, maintient la connexion et renvoie éventuellement une réponse au client. Votre code (contrôleur ou servlet) se situe quelque part au milieu.

Considérez ce flux:

  1. Tomcat obtient la demande du client.
  2. Tomcat exécute votre contrôleur.
  3. Relâchez le thread Tomcat mais conservez la connexion client (ne retournez pas de réponse) et exécutez un traitement intensif sur un autre thread.
  4. Une fois votre traitement lourd terminé, mettez à jour Tomcat avec sa réponse et renvoyez-le au client (par Tomcat).

Parce que le servlet (votre code) et le conteneur de servlet (Tomcat) sont des entités différentes, pour autoriser ce flux (libérer le thread Tomcat mais garder la connexion client), nous devons avoir ce support dans leur contrat , le paquet javax.servlet, introduit dans Servlet 3. . Maintenant, pour en revenir à votre question, Spring MVC utilise la nouvelle fonctionnalité Servlet 3.0 lorsque la valeur de retour du contrôleur est DeferredResult ou Callable, bien qu'il s'agisse de deux choses différentes. Callable est une interface qui fait partie de Java.util, et c'est une amélioration pour l'interface Runnable (devrait être implémentée par toute classe dont les instances sont destinées à être exécutées par un thread). Callable permet de renvoyer une valeur, contrairement à Runnable. DeferredResult est une classe conçue par Spring pour permettre plus d'options (que je décrirai) pour le traitement des demandes asynchrones dans Spring MVC, et ceci classe contient simplement le résultat (comme l'indique son nom) tandis que votre implémentation Callable contient le code asynchrone. Cela signifie donc que vous pouvez utiliser les deux dans votre contrôleur, exécuter votre code asynchrone avec Callable et définir le résultat dans DeferredResult, qui sera la valeur de retour du contrôleur. Alors qu'obtenez-vous en utilisant DeferredResult comme valeur de retour au lieu de Callable? DeferredResult a des rappels intégrés comme onError, onTimeout et onCompletion. Cela rend la gestion des erreurs très facile. De plus, comme il ne s'agit que du conteneur de résultats, vous pouvez choisir n'importe quel thread (ou pool de threads) à exécuter sur votre code asynchrone. Avec Callable, vous n'avez pas ce choix.

En ce qui concerne @Async , c'est beaucoup plus simple - annoter une méthode d'un bean avec @Async le fera s'exécuter dans un thread séparé. Par défaut (peut être remplacé), Spring utilise un SimpleAsyncTaskExecutor pour réellement exécuter ces méthodes de manière asynchrone.

En conclusion, si vous souhaitez libérer le thread Tomcat et conserver la connexion avec le client pendant que vous effectuez un traitement intensif, votre contrôleur doit retourner Callable ou DeferredResult. Sinon, vous pouvez exécuter le code sur la méthode annotée avec @Async.

6
KernelMode

DeferredResult tire parti du Servlet 3.0 AsyncContext. Il ne bloquera pas le thread comme les autres lorsque vous aurez besoin d'un résultat retourné.

Un autre gros avantage est que DeferredResult prend en charge les rappels.

3
Bart