The Spring @Async Annotation enables asynchronous processing.

The @Async annotation in Spring enables the creation of asynchronous methods. In this tutorial on the Spring framework, we will delve into the usage of @Async. When we apply the @Async annotation to a method of a bean, Spring will execute it in a separate thread, allowing the caller of the method to proceed without waiting for its completion. For this example, we will define a custom Service and utilize Spring Boot 2. Let’s begin!

Example of Spring’s @Async implementation for asynchronous programming.

To demonstrate, we will utilize Maven to generate a sample project. In order to create the project, simply run the given command in the desired workspace directory.

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

When running Maven for the first time, it may take a few seconds to complete the generate command as Maven needs to download the necessary plugins and artifacts for the generation task. The following image shows how project creation appears:Creating a project with MavenAfter creating the project, you can open it in your preferred IDE. The next step is to add the appropriate Maven Dependencies to the project. The pom.xml file with the relevant dependencies can be found below:

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

In order to comprehend all the JARs incorporated into the project after adding this dependency, we can execute a basic Maven command that displays a comprehensive Dependency Tree. The following command can be utilized:

mvn dependency:tree

If we execute this command, it will display the Dependency Tree mentioned below.

Allowing the use of asynchronous support.

Enabling Async support is also just a matter of a single annotation. In addition to enabling Async execution, we will also utilize an Executor to define Thread limits. We will discuss this further once we start coding.

package com.scdev.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 {
    ...
}

We utilized the @EnableAsync annotation to activate Spring’s capability to execute methods asynchronously in a separate thread pool. Additionally, we included the specified Executor.

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

In this code, we specify that only a maximum of 2 threads can run at the same time and the queue size is limited to 500. Below is the entire class code with its import statements.

package com.scdev.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 will create a service that truly utilizes Thread executions.

Creating a prototype

We plan to utilize a publicly available Movie API that solely provides information about movies. Our objective is to create our own model for the API.

package com.scdev.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);
    }
}

We have utilized @JsonIgnoreProperties to ensure that any additional attributes in the response can be safely disregarded by Spring.

Providing the service.

It is time for us to establish our Service that will make requests to the Movie API mentioned. We will utilize a basic RestTemplate to make a GET API call and fetch results asynchronously. Now, let’s examine the code snippet we employ.

package com.scdev.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);
    }
}

This class is marked as a @Service, allowing it to be scanned by Spring Component Scan. The return type of the lookForMovie method is CompletableFuture, as is necessary for any asynchronous service. To demonstrate the potential timing differences of the API, we have included a 2-second delay.

Creating a Command Line Runner

We will execute our app by utilizing a CommandLineRunner, which is the simplest method to test our application. A CommandLineRunner executes immediately after all the beans in the application have been initialized. Now, let’s examine the code for the CommandLineRunner.

package com.scdev.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<MovieModel> page1 = movieService.lookForMovie("58611129-2dbc-4a81-a72f-77ddfc1b1b49");
        CompletableFuture<MovieModel> page2 = movieService.lookForMovie("2baf70d1-42bb-4437-b551-e5fed5a87abe");
        CompletableFuture<MovieModel> 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
        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
        LOG.info("--> " + page1.get());
        LOG.info("--> " + page2.get());
        LOG.info("--> " + page3.get());
    }
}

We recently utilized the RestTemplate to query the example API with a selection of randomly chosen Movie IDs. The application will now be executed to observe the resulting output.

Operating the software.

Once the application is executed, the output displayed will be as follows.

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

If you take a closer look, you will notice that only two threads were specifically created for execution within the app: JDAsync-1 and JDAsync-2.

In summary, that is the final outcome.

In this tutorial, we explored the utilization of Spring Boot 2’s asynchronous features in conjunction with Spring. For additional Spring-related articles, please click here.

Please fetch the Source Code.

Get the Spring Boot Async Example Project for download.

 

More tutorials

Spring Component annotation(Opens in a new browser tab)

Spring MVC Controller(Opens in a new browser tab)

error Attempting to install Java on a MacBook.(Opens in a new browser tab)

The Spring Framework(Opens in a new browser tab)

Spring Boot CLI(Opens in a new browser tab)

Leave a Reply 0

Your email address will not be published. Required fields are marked *


广告
Closing in 10 seconds
bannerAds