Guide des résultats différés au printemps

1. Vue d'ensemble

Dans ce didacticiel, nous verrons comment nous pouvons utiliser la classe DeferredResult dans Spring MVC pour effectuer le traitement des requêtes asynchrones .

Le support asynchrone a été introduit dans Servlet 3.0 et, en termes simples, il permet de traiter une requête HTTP dans un autre thread que le thread récepteur de requête.

DeferredResult, disponible à partir de Spring 3.2, aide à décharger un calcul de longue durée d'un thread de travail http vers un thread séparé.

Bien que l'autre thread prenne certaines ressources pour le calcul, les threads de travail ne sont pas bloqués entre-temps et peuvent gérer les demandes client entrantes.

Le modèle de traitement des demandes asynchrones est très utile car il permet de bien mettre à l'échelle une application lors de charges élevées, en particulier pour les opérations intensives d'E / S.

2. Configuration

Pour nos exemples, nous utiliserons une application Spring Boot. Pour plus de détails sur la façon de démarrer l'application, reportez-vous à notre article précédent.

Ensuite, nous démontrerons à la fois la communication synchrone et asynchrone à l'aide de DeferredResult et comparerons également comment un asynchrone évolue mieux pour les cas d'utilisation à forte charge et intensifs d'E / S.

3. Blocage du service REST

Commençons par développer un service REST de blocage standard:

@GetMapping("/process-blocking") public ResponseEntity handleReqSync(Model model) { // ... return ResponseEntity.ok("ok"); }

Le problème ici est que le thread de traitement de la demande est bloqué jusqu'à ce que la demande complète soit traitée et que le résultat soit renvoyé. Dans le cas de calculs de longue durée, il s'agit d'une solution sous-optimale.

Pour résoudre ce problème, nous pouvons mieux utiliser les threads de conteneur pour gérer les demandes des clients, comme nous le verrons dans la section suivante.

4. REST non bloquant utilisant DeferredResult

Pour éviter le blocage, nous utiliserons un modèle de programmation basé sur les rappels où, au lieu du résultat réel, nous retournerons un DeferredResult au conteneur de servlet.

@GetMapping("/async-deferredresult") public DeferredResult
    
      handleReqDefResult(Model model) { LOG.info("Received async-deferredresult request"); DeferredResult
     
       output = new DeferredResult(); ForkJoinPool.commonPool().submit(() -> { LOG.info("Processing in separate thread"); try { Thread.sleep(6000); } catch (InterruptedException e) { } output.setResult(ResponseEntity.ok("ok")); }); LOG.info("servlet thread freed"); return output; }
     
    

Le traitement des requêtes est effectué dans un thread séparé et une fois terminé, nous appelons l' opération setResult sur l' objet DeferredResult .

Regardons la sortie du journal pour vérifier que nos threads se comportent comme prévu:

[nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController: Received async-deferredresult request [nio-8080-exec-6] com.baeldung.controller.AsyncDeferredResultController: Servlet thread freed [nio-8080-exec-6] java.lang.Thread : Processing in separate thread

En interne, le thread de conteneur est notifié et la réponse HTTP est délivrée au client. La connexion restera ouverte par le conteneur (servlet 3.0 ou version ultérieure) jusqu'à ce que la réponse arrive ou expire.

5. Rappels DeferredResult

Nous pouvons enregistrer 3 types de rappels avec un DeferredResult: les rappels d'achèvement, de délai d'expiration et d'erreur.

Utilisons la méthode onCompletion () pour définir un bloc de code qui est exécuté lorsqu'une requête asynchrone se termine:

deferredResult.onCompletion(() -> LOG.info("Processing complete"));

De même, nous pouvons utiliser onTimeout () pour enregistrer du code personnalisé à invoquer une fois le délai écoulé . Afin de limiter le temps de traitement des demandes, nous pouvons passer une valeur de timeout lors de la création de l'objet DeferredResult :

DeferredResult
    
      deferredResult = new DeferredResult(500l); deferredResult.onTimeout(() -> deferredResult.setErrorResult( ResponseEntity.status(HttpStatus.REQUEST_TIMEOUT) .body("Request timeout occurred.")));
    

En cas d'expiration de délai, nous définissons un état de réponse différent via le gestionnaire de délai d'expiration enregistré avec DeferredResult .

Déclenchons une erreur de timeout en traitant une requête qui prend plus que les valeurs de timeout définies de 5 secondes:

ForkJoinPool.commonPool().submit(() -> { LOG.info("Processing in separate thread"); try { Thread.sleep(6000); } catch (InterruptedException e) { ... } deferredResult.setResult(ResponseEntity.ok("OK"))); });

Regardons les journaux:

[nio-8080-exec-6] com.baeldung.controller.DeferredResultController: servlet thread freed [nio-8080-exec-6] java.lang.Thread: Processing in separate thread [nio-8080-exec-6] com.baeldung.controller.DeferredResultController: Request timeout occurred

Il y aura des scénarios où le calcul de longue durée échoue en raison d'une erreur ou d'une exception. Dans ce cas, nous pouvons également enregistrer un rappel onError () :

deferredResult.onError((Throwable t) -> { deferredResult.setErrorResult( ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body("An error occurred.")); });

En cas d'erreur, lors du calcul de la réponse, nous définissons un statut de réponse et un corps de message différents via ce gestionnaire d'erreurs.

6. Conclusion

Dans cet article rapide, nous avons examiné comment Spring MVC DeferredResult facilite la création de points de terminaison asynchrones.

Comme d'habitude, le code source complet est disponible sur Github.