Spring @Async Annotation für asynchrones Verarbeiten
Die Spring @Async Annotation ermöglicht es uns, asynchrone Methoden in Spring zu erstellen. Lassen Sie uns @Async in diesem Tutorial über das Spring Framework erkunden. Wir werden unseren eigenen Service definieren und in diesem Beispiel Spring Boot 2 verwenden. Lassen Sie uns beginnen!
Spring @Async Annotation 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 werden:
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 Generate-Befehl auszuführen, da Maven alle erforderlichen Plugins und Artefakte herunterladen muss, um die Generierungsaufgabe zu erledigen.
Erstellung eines Projekts mit Maven
Sobald Sie das Projekt erstellt haben, können Sie es in Ihrer bevorzugten IDE öffnen. Der nächste Schritt ist das Hinzufügen der passenden 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/>
</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>
Schließlich, um alle JARs zu verstehen, die dem Projekt hinzugefügt wurden, als wir diese Abhängigkeit hinzugefügt haben, können wir einen einfachen Maven-Befehl ausführen, der es uns ermöglicht, einen kompletten 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
Aktivierung der Async-Unterstützung
Die Aktivierung der Async-Unterstützung ist ebenfalls nur eine Frage einer einzigen Annotation. Abgesehen von der Aktivierung der asynchronen Ausführung werden wir auch Executor verwenden, die es uns ermöglichen, Thread-Limits zu definieren. Mehr dazu, wenn 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 es Spring ermöglicht, asynchrone Methoden in einem Hintergrund-Thread-Pool auszuführen. Als nächstes fügen wir auch den erwähnten 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 haben wir festgelegt, dass maximal 2 Threads gleichzeitig ausgeführt werden sollen und die Warteschlangengröße auf 500 eingestellt 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 nutzt.
Spring @Async Annotation – Erstellung 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 getters and setters
@Override
public String toString() {
return String.format("MovieModel{title='%s', producer='%s'}", title, producer);
}
}
Wir haben @JsonIgnoreProperties verwendet, damit, wenn es mehr Attribute in der Antwort gibt, diese sicher von Spring ignoriert werden können.
Erstellung des Services
Es ist an der Zeit, unseren Service zu definieren, der die erwähnte Movie API aufrufen wird. Wir werden ein einfaches RestTemplate verwenden, um eine GET API zu treffen und Ergebnisse asynchron zu erhalten. Lassen Sie uns den Beispielcode betrachten, 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);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}
}
Diese Klasse ist ein @Service, was sie für den Spring-Komponenten-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 zu Demonstrationszwecken hinzugefügt.
Spring @Async Annotation – Erstellung eines Command Line Runners
Wir werden unsere App mit einem CommandLineRunner ausführen, der der einfachste Weg ist, um unsere Anwendung zu testen. Ein CommandLineRunner wird unmittelbar nachdem alle Beans der Anwendung initialisiert wurden ausgeführt. Lassen Sie uns den Code für CommandLineRunner sehen:
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 the clock
long start = System.currentTimeMillis();
// Kick of multiple, asynchronous lookups
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");
// Join all threads so that we can wait until all are done
CompletableFuture.allOf(page1, page2, page3).join();
// Print results, including elapsed time // Print results, including elapsed time
LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
LOG.info("--> " + page1.get());
LOG.info("--> " + page2.get());
LOG.info("--> " + page3.get());
}
}
Wir haben gerade das RestTemplate verwendet, um die Beispiel-API, die wir verwendet haben, mit einigen zufällig ausgewählten Film-IDs zu treffen. Wir werden unsere Anwendung ausführen, um zu sehen, welche Ausgabe sie zeigt.
Spring @Async Annotation – Anwendung ausführen
Wenn wir die Anwendung ausführen, werden wir die folgende Ausgabe sehen:
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 gemacht, nämlich JDAsync-1 und JDAsync-2.
Fazit
In dieser Lektion haben wir untersucht, wie wir die asynchronen Fähigkeiten von Spring mit Spring Boot 2 Processing nutzen können – asynchrones Verarbeiten