Annotation @Async di Spring per Elaborazione Asincrona

La annotazione @Async di Spring ci permette di creare metodi asincroni in spring. Esploriamo @Async in questo tutorial sul framework di Spring. In breve, quando annotiamo un metodo di un bean con l’annotazione @Async, Spring lo eseguirà in un thread separato e il chiamante del metodo non aspetterà che il metodo completi l’esecuzione. Definiremo il nostro proprio Servizio e utilizzeremo Spring Boot 2 in questo esempio. Cominciamo!

Spring @Async Esempio

Utilizzeremo Maven per creare un progetto di esempio per la dimostrazione. Per creare il progetto, esegui il seguente comando in una directory che utilizzerai come spazio di lavoro:

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

Se stai eseguendo Maven per la prima volta, ci vorranno alcuni secondi per completare il comando di generazione perché Maven deve scaricare tutti i plugin e gli artefatti necessari per svolgere il compito di generazione. Ecco come appare la creazione del progetto: Una volta creato il progetto, sentiti libero di aprirlo nel tuo IDE preferito. Il passo successivo è aggiungere le dipendenze appropriate di Maven al progetto. Ecco il file pom.xml con le dipendenze appropriate:

<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>

Infine, per capire tutti i JAR che vengono aggiunti al progetto quando aggiungiamo questa dipendenza, possiamo eseguire un semplice comando Maven che ci consente di vedere un completo Albero delle Dipendenze per un progetto quando vi aggiungiamo alcune dipendenze. Ecco un comando che possiamo usare:

mvn dependency:tree

Quando eseguiamo questo comando, ci mostrerà il seguente Albero delle Dipendenze:

Abilitazione del Supporto Async

Abilitare il supporto Async è anche una questione di una singola annotazione. Oltre ad abilitare l’esecuzione Async, faremo anche uso di Executor che ci permette di definire i limiti dei Thread. Più su questo una volta che scriviamo il codice:

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 {
    ...
}

Qui abbiamo fatto uso dell’annotazione @EnableAsync che abilita la capacità di Spring di eseguire metodi Asincroni in un pool di thread di background. Successivamente, aggiungiamo anche l’Executor menzionato:

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

Qui, impostiamo che un massimo di 2 thread dovrebbero essere eseguiti contemporaneamente e la dimensione della coda è impostata su 500. Ecco il codice completo della classe con le dichiarazioni di importazione:

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;
    }
}

Creeremo un servizio successivo che effettivamente fa uso delle esecuzioni dei thread.

Creare un Modello

Useremo un API di Film pubblico che restituisce solo i dati di un film. Definiremo il nostro modello per lo stesso:

package com.journaldev.asynchexample;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {

    private String title;
    private String producer;

    // standard getters e setters

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

Abbiamo usato @JsonIgnoreProperties in modo che se ci sono più attributi nella risposta, possano essere ignorati in modo sicuro da Spring.

Creazione del Servizio

È ora di definire il nostro servizio che chiamerà l’API del film menzionata. Utilizzeremo un semplice RestTemplate per richiamare un’API GET e ottenere risultati in modo asincrono. Vediamo il codice di esempio che utilizziamo:

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);
        // Ritardo artificiale di 1 secondo per scopi dimostrativi
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }
}

Questa classe è un @Service che lo rende idoneo alla scansione dei componenti Spring. Il tipo di ritorno del metodo lookForMovie è CompletableFuture, che è un requisito per qualsiasi servizio asincrono. Poiché il tempo di risposta dell’API può variare, abbiamo aggiunto un ritardo di 2 secondi per scopi dimostrativi.

Creazione di un Command Line Runner

Eseguiremo la nostra app utilizzando un CommandLineRunner, che è il modo più semplice per testare la nostra applicazione. Un CommandLineRunner viene eseguito subito dopo l’inizializzazione di tutti i bean dell’applicazione. Vediamo il codice per 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 {
        // Avvia il timer
        long start = System.currentTimeMillis();

        // Avvia ricerche multiple in modo asincrono
        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");

        // Attendiamo che tutti i thread siano completati
        CompletableFuture.allOf(page1, page2, page3).join();

        // Stampa i risultati, inclusi il tempo trascorso
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

Abbiamo utilizzato il RestTemplate per chiamare l’API di esempio con alcuni ID di film scelti casualmente. Eseguiremo la nostra applicazione per vedere quale output verrà mostrato.

Esecuzione dell’applicazione

Quando eseguiamo l’applicazione, vedremo il seguente output:

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'}

Se osservi attentamente, solo due thread sono stati creati per essere eseguiti nell’app, precisamente JDAsync-1 e JDAsync-2.

Conclusione

In questa lezione, abbiamo studiato come possiamo utilizzare le capacità asincrone di Spring con Spring Boot 2. Leggi altri post correlati a Spring qui.

Scarica il codice sorgente

Scarica il progetto di esempio di Spring Boot Async

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