web-dev-qa-db-fra.com

Comment faire un asynchrone REST avec Spring?

J'essaie de faire un petit REST en utilisant Spring Boot. Je n'ai jamais utilisé Spring et utilisé Java il y a longtemps (Java 7)!

Au cours des 2 dernières années, j'ai utilisé uniquement Python et C # (mais comme je l'ai dit, j'ai déjà utilisé Java).

Donc, maintenant, j'essaie de faire un REST en utilisant des méthodes asynchrones et de vérifier plusieurs exemples, mais je ne comprends toujours pas très bien la "manière correcte" de procéder.

En regardant la documentation suivante: http://carlmartensen.com/completablefuture-deferredresult-async , Java 8 a CompletableFuture que je peux utiliser avec Printemps, alors, j'ai fait le code suivant:

Service :

@Service
public class UserService {
  private UserRepository userRepository;

  // dependency injection
  // don't need Autowire here
  // https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
  public UserService(UserRepository userRepository) {
    this.userRepository = userRepository;
  }

  @Async
  public CompletableFuture<User> findByEmail(String email) throws InterrupedException {
    User user = userRepository.findByEmail(email);
    return CompletableFuture.completedFuture(user);
  }
}

Dépôt :

public interface UserRepository extends MongoRepository<User, String> {
  @Async
  findByEmail(String email);
}

RestController :

@RestController
public class TestController {

  private UserService userService;

  public TestController(UserService userService) {
    this.userService = userService;
  }

  @RequestMapping(value = "test")
  public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
    return userService.findByEmail(email).thenApplyAsync(user -> {
      return user;
    })
  }  
}

Ce code me donne la sortie attendue. Ensuite, en regardant une autre documentation (désolé, j'ai perdu le lien), je vois que Spring accepte le code suivant (qui me donne également le résultat attendu):

  @RequestMapping(value = "test")
  public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
    return userService.findByEmail(email);
  }  
}

Y a-t-il une différence entre les deux méthodes?

Ensuite, en regardant le guide suivant: https://spring.io/guides/gs/async-method/ , il y a un @EnableAsync annotation dans SpringBootApplication classe. Si j'inclus le @EnableAsync Annotation et créer un asyncExecutor Bean comme le code du dernier lien, mon application ne renvoie rien sur /test endpoint (seulement une réponse 200 OK, mais avec un corps vide).

Donc, mon repos est asynchrone sans le @EnableAsync annotation? Et pourquoi quand j’utilise @EnableAsync, le corps de la réponse est vide?

14
Roberto Correia

Le corps de la réponse est vide car l'annotation @Async Est utilisée dans la méthode findEmail de la classe UserRepository. Cela signifie qu'aucune donnée n'est retournée à la phrase suivante User user = userRepository.findByEmail(email); car la méthode findByEmail s'exécute sur d'autres thread et retournera null au lieu d'un objet List.

L'annotation @Async Est activée lorsque vous déclarez @EnableAsync, C'est pourquoi elle ne se produit que lorsque vous utilisez @EnableAsync Car elle active la méthode @Async de findEmail pour l'exécuter sur d'autres fil.

La méthode return userService.findByEmail(email); renverra un objet CompletableFuture créé à partir de la classe UserService.

La différence avec le second appel de méthode est que la méthode thenApplyAsync crée un tout nouveau CompletableFuture à partir de la précédente qui provient de userService.findByEmail(email) et ne renvoie que l'objet utilisateur fourni. à partir du premier CompletableFuture.

 return userService.findByEmail(email).thenApplyAsync(user -> {
      return user;
    })

Si vous souhaitez obtenir les résultats attendus, supprimez simplement l'annotation @Async De la méthode findByEmail, puis ajoutez l'annotation @EnableAsync.

Si vous devez clarifier vos idées sur l'utilisation des méthodes asynchrones, supposons qu'il soit nécessaire d'appeler trois méthodes et que chacune prend 2 secondes pour se terminer. Dans un scénario normal, vous les appellerez méthode1, puis méthode2 et enfin méthode3. toute la demande prendra 6 secondes. Lorsque vous activez l'approche Async, vous pouvez en appeler trois et attendre 2 secondes au lieu de 6.

Ajoutez cette méthode longue au service utilisateur:

@Async
public  CompletableFuture<Boolean> veryLongMethod()  {

    try {
        Thread.sleep(2000L);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

    return CompletableFuture.completedFuture(true);
}

Et appelez-le trois fois depuis le contrôleur, comme ceci

  @RequestMapping(value = "test")
  public @ResponseBody CompletableFuture<User> test(@RequestParam(value = "email", required = true) String email) throws InterruptedException {
        CompletableFuture<Boolean> boolean1= siteService.veryLongMethod();
        CompletableFuture<Boolean> boolean2= siteService.veryLongMethod();
        CompletableFuture<Boolean> boolean3= siteService.veryLongMethod();

        CompletableFuture.allOf(boolean1,boolean2,boolean3).join();
    return userService.findByEmail(email);
  }  

Enfin, mesurez le temps que prend votre réponse. Si cela prend plus de 6 secondes, vous n’exécutez pas la méthode Async. Si cela ne prend que 2 secondes, vous réussissez.

Consultez également la documentation suivante: @ Annotation Async , méthodes asynchrones Spring , classe CompletableFuture

J'espère que ça vous aidera.

11
Daniel C.

Je rencontre des problèmes de performances lors du déclenchement de méthodes asynchrones. Les fils enfants asynchrones commencent à s'exécuter très tard (délai d'environ 20 à 30 secondes). J'utilise ThreadPoolTaskExecutor () dans ma classe d'application SpringBoot principale. Vous pouvez également essayer la même chose si vous considérez la performance comme un facteur.

1
Kishor kumar R