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:
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 hetpom.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
Source:
https://www.digitalocean.com/community/tutorials/spring-async-annotation