Spring @Async-annotatie voor asynchrone verwerking

De annotatie @Async in Spring stelt ons in staat om asynchrone methoden te maken in Spring. Laten we @Async verkennen in deze tutorial over het Spring-framework. Kort gezegd, wanneer we een methode van een bean annoteren met de @Async-annotatie, zal Spring deze uitvoeren in een apart thread en de aanroeper van de methode zal niet wachten tot de methode is voltooid. We zullen onze eigen Service definiëren en Spring Boot 2 gebruiken in dit voorbeeld. Laten we beginnen!

Spring @Async Voorbeeld

We zullen Maven gebruiken om een voorbeeldproject te maken voor de demonstratie. Voer om het project te maken het volgende commando uit in een directory die je als werkruimte zult gebruiken:

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

Als je Maven voor de eerste keer uitvoert, zal het even duren voordat de `generate`-opdracht is voltooid, omdat Maven alle benodigde plugins en artifacts moet downloaden om de generatietaak uit te voeren. Zo ziet het projectcreatieproces eruit:

Project maken met Maven

Nadat je het project hebt aangemaakt, kun je het gerust openen in je favoriete IDE. De volgende stap is het toevoegen van de juiste Maven-afhankelijkheden aan het project. Hier is het pom.xml-bestand met de juiste afhankelijkheden:

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

Ten slotte, om te begrijpen welke JAR-bestanden aan het project zijn toegevoegd wanneer we deze afhankelijkheid hebben toegevoegd, kunnen we een eenvoudige Maven-opdracht uitvoeren waarmee we een volledige afhankelijkheidsstructuur voor een project kunnen zien wanneer we er wat afhankelijkheden aan toevoegen. Hier is een opdracht die we kunnen gebruiken:

mvn dependency:tree

Wanneer we deze opdracht uitvoeren, zal het ons de volgende afhankelijkheidsstructuur tonen:

Async Support inschakelen

Het inschakelen van Async-ondersteuning is ook slechts een kwestie van een enkele annotatie. Naast het inschakelen van de Async-uitvoering zullen we ook gebruik maken van een Executor die ons in staat stelt om Thread limieten te definiëren. Meer hierover zodra we de code schrijven:

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

Hier hebben we gebruik gemaakt van de @EnableAsync annotatie die de mogelijkheid van Spring om asynchrone methoden in een achtergrond-threadpool uit te voeren inschakelt. Vervolgens voegen we ook de genoemde Executor toe:

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

Hier stellen we in dat maximaal 2 threads gelijktijdig moeten worden uitgevoerd en de wachtrijgrootte is ingesteld op 500. Hier is de volledige code van de klasse met importverklaringen:

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

We zullen vervolgens een service maken die daadwerkelijk gebruik maakt van Thread-uitvoeringen.

Het maken van een model

We zullen gebruik maken van een openbare Film API die alleen de gegevens van een film retourneert. We zullen ons model voor hetzelfde definiëren:

package com.journaldev.asynchexample;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {

    private String title;
    private String producer;

     // standaard getters en setters 

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

We hebben @JsonIgnoreProperties gebruikt zodat als er meer attributen in de respons zijn, ze veilig kunnen worden genegeerd door Spring.

Het maken van de service

Het is tijd dat we onze service definiëren die de genoemde Movie API zal aanroepen. We zullen een eenvoudige RestTemplate gebruiken om een GET API aan te roepen en de resultaten asynchroon te verkrijgen. Laten we kijken naar de voorbeeldcode die we gebruiken:

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);
        // Kunstmatige vertraging van 1 seconde voor demonstratiedoeleinden
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }
}

Deze klasse is een @Service, wat het in aanmerking laat komen voor Spring Component Scan. Het retourtype van de methode lookForMovie is CompletableFuture, wat een vereiste is voor elke asynchrone service. Aangezien de timing voor de API kan variëren, hebben we een vertraging van 2 seconden toegevoegd voor demonstratie.

Maak een Command Line Runner

We zullen onze app uitvoeren met behulp van een CommandLineRunner, wat de eenvoudigste manier is om onze toepassing te testen. Een CommandLineRunner wordt direct uitgevoerd nadat alle beans van de toepassing zijn geïnitialiseerd. Laten we de code voor CommandLineRunner bekijken:

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 {
        // Start de klok
        long start = System.currentTimeMillis();

        // Start meerdere, asynchrone opzoekingen
        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");

        // Voeg alle threads samen zodat we kunnen wachten totdat alles is voltooid
        CompletableFuture.allOf(page1, page2, page3).join();

        // Resultaten afdrukken, inclusief verstreken tijd
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

We hebben eenvoudigweg de RestTemplate gebruikt om de voorbeeld-API aan te roepen met enkele willekeurig gekozen Movie ID’s. We zullen onze toepassing uitvoeren om te zien welke uitvoer het laat zien.

Wanneer we de toepassing uitvoeren, zien we de volgende uitvoer:

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

Als je goed kijkt, worden er slechts twee threads gemaakt om te worden uitgevoerd in de app, namelijk JDAsync-1 en JDAsync-2.

Conclusie

In deze les hebben we bestudeerd hoe we Spring’s asynchrone mogelijkheden kunnen gebruiken met Spring Boot 2. Lees meer Spring-gerelateerde berichten hier.

Download de broncode

Download Spring Boot Async Voorbeeldproject

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