Anotação @Async do Spring para Processamento Assíncrono

A anotação @Async do Spring nos permite criar métodos assíncronos no Spring. Vamos explorar @Async neste tutorial sobre o framework Spring. Em resumo, quando anotamos um método de um bean com a anotação @Async, o Spring irá executá-lo em uma thread separada e o chamador do método não irá esperar até que o método seja concluído. Vamos definir nosso próprio Serviço e usar o Spring Boot 2 neste exemplo. Vamos começar!

Exemplo de @Async no Spring

Vamos usar o Maven para criar um projeto de exemplo para a demonstração. Para criar o projeto, execute o seguinte comando em um diretório que você usará como espaço de trabalho:

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

Se você estiver executando o Maven pela primeira vez, levará alguns segundos para concluir o comando de geração porque o Maven precisa baixar todos os plugins e artefatos necessários para realizar a tarefa de geração. Aqui está como se parece a criação do projeto:

Criando um projeto com Maven

Depois de criar o projeto, sinta-se à vontade para abri-lo em sua IDE favorita. O próximo passo é adicionar as dependências apropriadas do Maven ao projeto. Aqui está o arquivo pom.xml com as dependências apropriadas:

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

Finalmente, para entender todos os JARs que são adicionados ao projeto quando adicionamos essa dependência, podemos executar um simples comando do Maven que nos permite ver uma árvore completa de dependências para um projeto quando adicionamos algumas dependências a ele. Aqui está um comando que podemos usar:

mvn dependency:tree

Ao executar este comando, ele nos mostrará a seguinte árvore de dependências:

Habilitando Suporte Assíncrono

Habilitar o suporte assíncrono é também apenas uma questão de uma única anotação. Além de habilitar a execução assíncrona, também faremos uso do Executor, que nos permite definir limites de Threads também. Mais sobre isso uma vez que escrevemos o código:

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

Aqui fizemos uso da anotação @EnableAsync, que habilita a capacidade do Spring de executar métodos assíncronos em um pool de threads em segundo plano. Em seguida, também adicionamos o Executor mencionado:

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

Aqui, definimos que no máximo 2 threads devem ser executadas simultaneamente e o tamanho da fila é definido como 500. Aqui está o código completo da classe com as declarações de importação:

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

Vamos fazer um serviço em seguida que realmente faz execuções de Threads.

Criando um Modelo

Vamos usar uma API de Filmes pública que apenas retorna os dados de um filme. Vamos definir nosso modelo para o mesmo:

package com.journaldev.asynchexample;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {

    private String title;
    private String producer;

    // getters e setters padrão

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

Nós usamos @JsonIgnoreProperties para que, se houver mais atributos na resposta, eles possam ser ignorados com segurança pelo Spring.

Criando o Serviço

É hora de definirmos nosso Serviço que estará chamando a API de Filmes mencionada. Vamos usar um RestTemplate simples para acessar uma API GET e obter resultados de forma assíncrona. Vamos dar uma olhada no código de exemplo que estamos usando:

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);
        // Atraso artificial de 1 segundo para fins de demonstração
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }
}

Esta classe é um @Service, o que a torna elegível para a Verificação de Componentes do Spring. O tipo de retorno do método lookForMovie é CompletableFuture, o que é um requisito para qualquer serviço assíncrono. Como o tempo para a API pode variar, adicionamos um atraso de 2 segundos para demonstração.

Criando um CommandLine Runner

Vamos executar nosso aplicativo usando um CommandLineRunner, que é a maneira mais fácil de testar nossa aplicação. Um CommandLineRunner é executado logo após todos os beans da aplicação serem inicializados. Vamos ver o código para 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 {
        // Iniciar o cronômetro
        long start = System.currentTimeMillis();

        // Iniciar várias consultas assíncronas
        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");

        // Juntar todos os threads para que possamos esperar até que todos estejam prontos
        CompletableFuture.allOf(page1, page2, page3).join();

        // Imprimir resultados, incluindo o tempo decorrido
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

Apenas usamos o RestTemplate para acessar a API de exemplo que usamos com alguns IDs de Filme selecionados aleatoriamente. Vamos executar nosso aplicativo para ver qual saída ele mostra.

Executando a aplicação

Ao executarmos a aplicação, veremos a seguinte saída:

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 observarmos atentamente, apenas dois threads foram criados para serem executados na aplicação, a saber, JDAsync-1 e JDAsync-2.

Conclusão

Nesta lição, estudamos como podemos usar as capacidades assíncronas do Spring com o Spring Boot 2. Leia mais postagens relacionadas ao Spring aqui.

Baixe o Código Fonte

Baixe o Projeto de Exemplo Spring Boot Async

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