Spring @Async-Annotation für asynchrone Verarbeitung

Frühling @Async-Annotation ermöglicht es uns, asynchrone Methoden in Spring zu erstellen. Lassen Sie uns die @Async-Annotation in diesem Tutorial zum Spring-Framework erkunden. Kurz gesagt, wenn wir eine Methode einer Bean mit der @Async-Annotation annotieren, wird Spring sie in einem separaten Thread ausführen, und der Aufrufer der Methode wartet nicht, bis die Methode abgeschlossen ist. In diesem Beispiel werden wir unseren eigenen Service definieren und Spring Boot 2 verwenden. Lassen Sie uns beginnen!

Spring @Async Beispiel

Wir werden Maven verwenden, um ein Beispielprojekt für die Demonstration zu erstellen. Um das Projekt zu erstellen, führen Sie den folgenden Befehl in einem Verzeichnis aus, das Sie als Arbeitsbereich verwenden möchten:

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

Wenn Sie Maven zum ersten Mal ausführen, dauert es einige Sekunden, um den Generierungsbefehl abzuschließen, da Maven alle erforderlichen Plugins und Artefakte herunterladen muss, um die Generierungsaufgabe zu ermöglichen. So sieht die Projekt erstellung aus: Sobald Sie das Projekt erstellt haben, können Sie es in Ihrer Lieblings-IDE öffnen. Der nächste Schritt ist das Hinzufügen der entsprechenden Maven-Abhängigkeiten zum Projekt. Hier ist die pom.xml-Datei mit den entsprechenden Abhängigkeiten:

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

Zum Verständnis aller JARs, die dem Projekt hinzugefügt werden, wenn wir diese Abhängigkeit hinzufügen, können wir einen einfachen Maven-Befehl ausführen, der es uns ermöglicht, einen vollständigen Abhängigkeitsbaum für ein Projekt zu sehen, wenn wir einige Abhängigkeiten hinzufügen. Hier ist ein Befehl, den wir verwenden können:

mvn dependency:tree

Wenn wir diesen Befehl ausführen, zeigt er uns den folgenden Abhängigkeitsbaum:

Async-Support aktivieren

Das Aktivieren der Async-Unterstützung ist ebenfalls nur eine Frage einer einzigen Annotation. Neben der Aktivierung der Async-Ausführung werden wir auch einen Executor verwenden, der es uns ermöglicht, Thread-Limits festzulegen. Mehr dazu, sobald wir den Code schreiben:

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 haben wir die @EnableAsync-Annotation verwendet, die die Fähigkeit von Spring aktiviert, asynchrone Methoden in einem Hintergrund-Thread-Pool auszuführen. Als nächstes fügen wir den genannten Executor hinzu:

@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 legen wir fest, dass maximal 2 Threads gleichzeitig ausgeführt werden sollen und die Warteschlangengröße auf 500 festgelegt ist. Hier ist der vollständige Code der Klasse mit Import-Anweisungen:

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

Als nächstes werden wir einen Service erstellen, der tatsächlich Thread-Ausführungen durchführt.

Erstellen eines Modells

Wir werden eine öffentliche Movie-API verwenden, die nur die Daten eines Films zurückgibt. Wir werden unser Modell dafür definieren:

package com.journaldev.asynchexample;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {

    private String title;
    private String producer;

     // Standard-Getter und -Setter 

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

Wir haben @JsonIgnoreProperties verwendet, damit bei einer größeren Anzahl von Attributen in der Antwort diese sicher von Spring ignoriert werden können.

Erstellen des Dienstes

Es ist an der Zeit, unseren Service zu definieren, der die genannte Movie API aufruft. Wir werden ein einfaches RestTemplate verwenden, um eine GET-API aufzurufen und Ergebnisse asynchron zu erhalten. Schauen wir uns den Beispielcode an, den wir verwenden:

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);
        // Künstliche Verzögerung von 1s zu Demonstrationszwecken
        Thread.sleep(1000L);
        return CompletableFuture.completedFuture(results);
    }
}

Diese Klasse ist ein @Service, was sie für den Spring Component Scan qualifiziert. Der Rückgabetyp der Methode lookForMovie ist CompletableFuture, was eine Anforderung für jeden asynchronen Service ist. Da die Zeit für die API variieren kann, haben wir eine Verzögerung von 2 Sekunden für die Demonstration hinzugefügt.

Erstellung eines Command Line Runner

Wir werden unsere App mit einem CommandLineRunner ausführen, der der einfachste Weg ist, unsere Anwendung zu testen. Ein CommandLineRunner wird direkt nach der Initialisierung aller Beans der Anwendung ausgeführt. Schauen wir uns den Code für den CommandLineRunner an:

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 {
        // Starte die Uhr
        long start = System.currentTimeMillis();

        // Starte mehrere asynchrone Abfragen
        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");

        // Warte, bis alle Threads fertig sind
        CompletableFuture.allOf(page1, page2, page3).join();

        // Ergebnisse anzeigen, einschließlich der vergangenen Zeit
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

Wir haben einfach das RestTemplate verwendet, um die Beispiel-API mit einigen zufällig ausgewählten Movie-IDs aufzurufen. Wir werden unsere Anwendung ausführen, um zu sehen, welche Ausgabe sie zeigt.

Das Ausführen der Anwendung

Wenn wir die Anwendung ausführen, sehen wir die folgende Ausgabe:

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

Wenn Sie genau hinsehen, wurden nur zwei Threads zur Ausführung in der App erstellt, nämlich JDAsync-1 und JDAsync-2.

Schlussfolgerung

In dieser Lektion haben wir untersucht, wie wir die asynchronen Fähigkeiten von Spring mit Spring Boot 2 nutzen können. Lesen Sie mehr Spring-bezogene Beiträge hier.

Quellcode herunterladen

Spring Boot Async Beispielprojekt herunterladen

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