Annotation @Async Spring pour le traitement asynchrone

La balise @Async de Spring nous permet de créer des méthodes asynchrones dans Spring. Explorons @Async dans ce tutoriel sur le framework Spring. Pour faire simple, lorsque nous annotons une méthode d’un bean avec @Async, Spring l’exécutera dans un thread séparé et l’appelant de la méthode n’attendra pas que la méthode ait terminé son exécution. Nous allons définir notre propre service et utiliser Spring Boot 2 dans cet exemple. Commençons !

Exemple de Spring @Async

Nous utiliserons Maven pour créer un projet d’exemple pour la démonstration. Pour créer le projet, exécutez la commande suivante dans un répertoire que vous utiliserez comme espace de travail :

mvn archetype:generate -DgroupId=com.journaldev.asynchmethods -DartifactId=JD-SpringBoot-AsyncMethods -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Si vous exécutez Maven pour la première fois, cela prendra quelques secondes pour accomplir la commande generate car Maven doit télécharger tous les plugins et artefacts requis afin d’effectuer la tâche de génération. Voici à quoi ressemble la création de projet: Une fois que vous avez créé le projet, n’hésitez pas à l’ouvrir dans votre IDE préféré. L’étape suivante consiste à ajouter les dépendances Maven appropriées au projet. Voici le fichier pom.xml avec les dépendances appropriées:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.0.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

Enfin, pour comprendre tous les JARs qui sont ajoutés au projet lorsque nous ajoutons cette dépendance, nous pouvons exécuter une commande Maven simple qui nous permet de voir un arbre de dépendances complet pour un projet lorsque nous lui ajoutons des dépendances. Voici une commande que nous pouvons utiliser:

mvn dependency:tree

Lorsque nous exécutons cette commande, elle nous montrera l’arbre de dépendances suivant:

Activation du support asynchrone

Activer la prise en charge asynchrone est également une simple question d’une seule annotation. En plus d’activer l’exécution asynchrone, nous utiliserons également un Executor qui nous permet de définir des limites de thread. Plus de détails une fois que nous aurons écrit le code :

package com.journaldev.asynchexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class AsyncApp {
    ...
}

Ici, nous avons utilisé l’annotation @EnableAsync qui permet à Spring d’exécuter des méthodes asynchrones dans un pool de threads en arrière-plan. Ensuite, nous ajoutons également l’Executor mentionné :

@Bean
public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(2);
    executor.setQueueCapacity(500);
    executor.setThreadNamePrefix("JDAsync-");
    executor.initialize();
    return executor;
}

Ici, nous définissons que maximum 2 threads doivent s’exécuter simultanément et la taille de la file d’attente est fixée à 500. Voici le code complet de la classe avec les déclarations d’importation :

package com.journaldev.asynchexample;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@SpringBootApplication
@EnableAsync
public class AsyncApp {

    public static void main(String[] args) {
        SpringApplication.run(AsyncApp.class, args).close();
    }

    @Bean
    public Executor asyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(2);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("JDAsync-");
        executor.initialize();
        return executor;
    }
}

Nous créerons ensuite un service qui effectue réellement des exécutions de threads.

Création d’un modèle

Nous utiliserons une API de films publique qui renvoie simplement les données d’un film. Nous définirons notre modèle pour cela :

package com.journaldev.asynchexample;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {

    private String title;
    private String producer;

    // getters et setters standard

    @Override
    public String toString() {
        return String.format("MovieModel{title='%s', producer='%s'}", title, producer);
    }
}

Nous avons utilisé @JsonIgnoreProperties afin que, s’il y a plus d’attributs dans la réponse, ils puissent être ignorés en toute sécurité par Spring.

Création du service

Il est temps de définir notre Service qui appellera l’API de film mentionnée. Nous utiliserons un simple RestTemplate pour accéder à une API GET et obtenir des résultats de manière asynchrone. Examinons le code d’exemple que nous utilisons :

package com.journaldev.asynchexample;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.concurrent.CompletableFuture;

@Service
public class MovieService {

    private static final Logger LOG = LoggerFactory.getLogger(MovieService.class);

    private final RestTemplate restTemplate;

    public MovieService(RestTemplateBuilder restTemplateBuilder) {
        this.restTemplate = restTemplateBuilder.build();
    }

    @Async
    public CompletableFuture lookForMovie(String movieId) throws InterruptedException {
        LOG.info("Looking up Movie ID: {}", movieId);
        String url = String.format("https://ghibliapi.herokuapp.com/films/%s", movieId);
        MovieModel results = restTemplate.getForObject(url, MovieModel.class);
        // Délai artificiel de 1s à des fins de démonstration
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }
}

Cette classe est un @Service ce qui la rend éligible à la balayage des composants Spring. Le type de retour de la méthode lookForMovie est CompletableFuture ce qui est une exigence pour tout service asynchrone. Comme le timing de l’API peut varier, nous avons ajouté un délai de 2 secondes à des fins de démonstration.

Création d’un CommandLine Runner

Nous allons exécuter notre application en utilisant un CommandLineRunner qui est le moyen le plus simple de tester notre application. Un CommandLineRunner s’exécute juste après que tous les beans de l’application ont été initialisés. Voyons le code du CommandLineRunner :

package com.journaldev.asynchexample;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class ApplicationRunner implements CommandLineRunner {

    private static final Logger LOG = LoggerFactory.getLogger(ApplicationRunner.class);

    private final MovieService movieService;

    public ApplicationRunner(MovieService movieService) {
        this.movieService = movieService;
    }


    @Override
    public void run(String... args) throws Exception {
        // Démarrer l'horloge
        long start = System.currentTimeMillis();

        // Lancer plusieurs recherches asynchrones
        CompletableFuture page1 = movieService.lookForMovie("58611129-2dbc-4a81-a72f-77ddfc1b1b49");
        CompletableFuture page2 = movieService.lookForMovie("2baf70d1-42bb-4437-b551-e5fed5a87abe");
        CompletableFuture page3 = movieService.lookForMovie("4e236f34-b981-41c3-8c65-f8c9000b94e7");

        // Joindre tous les threads afin que nous puissions attendre que tous soient terminés
        CompletableFuture.allOf(page1, page2, page3).join();

        // Imprimer les résultats, y compris le temps écoulé
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

Nous venons d’utiliser RestTemplate pour accéder à l’API d’exemple que nous avons utilisée avec quelques identifiants de film choisis au hasard. Nous allons exécuter notre application pour voir quelle sortie elle affiche.

Exécution de l’application

Lorsque nous exécutons l’application, nous verrons la sortie suivante :

2018-04-13  INFO 17868 --- [JDAsync-1] c.j.a.MovieService  : Looking up Movie ID: 58611129-2dbc-4a81-a72f-77ddfc1b1b49
2018-04-13 08:00:09.518  INFO 17868 --- [JDAsync-2] c.j.a.MovieService  : Looking up Movie ID: 2baf70d1-42bb-4437-b551-e5fed5a87abe
2018-04-13 08:00:12.254  INFO 17868 --- [JDAsync-1] c.j.a.MovieService  : Looking up Movie ID: 4e236f34-b981-41c3-8c65-f8c9000b94e7
2018-04-13 08:00:13.565  INFO 17868 --- [main] c.j.a.ApplicationRunner  : Elapsed time: 4056
2018-04-13 08:00:13.565  INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='My Neighbor Totoro', producer='Hayao Miyazaki'}
2018-04-13 08:00:13.565  INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='Castle in the Sky', producer='Isao Takahata'}
2018-04-13 08:00:13.566  INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='Only Yesterday', producer='Toshio Suzuki'}

Si vous observez attentivement, seuls deux threads ont été créés pour être exécutés dans l’application, à savoir JDAsync-1 et JDAsync-2.

Conclusion

Dans cette leçon, nous avons étudié comment nous pouvons utiliser les capacités asynchrones de Spring avec Spring Boot 2. Lisez d’autres articles liés à Spring ici.

Télécharger le code source

Télécharger le projet exemple Spring Boot Async

Source:
https://www.digitalocean.com/community/tutorials/spring-async-annotation