用于异步处理的 Spring @Async 注解

春天 @Async 注释允许我们在春天创建非同步方法. 让我们在这个春天框架教程中探索 `@Async。 简而言之,当我们注释一种方法时,春天会在一个单独的 [线程]( / 社区 / 教程 / java 线程 - 例子)中执行它,而方法的召唤者不会等到方法完成执行。 我们将在本例中定义自己的服务并使用 Spring Boot 2。

春天 @Async 示例

我们将使用 Maven 创建示范项目示范项目. 要创建该项目,请在您将其用作工作区的目录中执行以下命令:

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

If you are running maven for the first time, it will take a few seconds to accomplish the generate command because maven has to download all the required plugins and artifacts in order to make the generation task. Here is how project creation looks like: [caption id="attachment_20460" align="aligncenter" width="860"]spring @async example maven projectCreating a project with Maven[/caption] Once you have created the project, feel free to open it in your favourite IDE. Next step is to add appropriate Maven Dependencies to the project. Here is the pom.xml file with the appropriate dependencies:

 1<parent>
 2    <groupId>org.springframework.boot</groupId>
 3    <artifactId>spring-boot-starter-parent</artifactId>
 4    <version>2.0.1.RELEASE</version>
 5    <relativePath/> <!-- lookup parent from repository -->
 6</parent>
 7
 8<dependencies>
 9
10    <dependency>
11        <groupId>org.springframework.boot</groupId>
12        <artifactId>spring-boot-starter-web</artifactId>
13    </dependency>
14
15    <dependency>
16        <groupId>org.springframework.boot</groupId>
17        <artifactId>spring-boot-starter-test</artifactId>
18        <scope>test</scope>
19    </dependency>
20
21</dependencies>
22
23<build>
24    <plugins>
25        <plugin>
26            <groupId>org.springframework.boot</groupId>
27            <artifactId>spring-boot-maven-plugin</artifactId>
28        </plugin>
29    </plugins>
30</build>

最后,要了解在我们添加这种依赖时添加到项目的所有JAR,我们可以运行一个简单的Maven命令,允许我们在添加一些依赖时看到一个项目的完整依赖树。

1mvn dependency:tree

When we run this command, it will show us the following Dependency Tree: spring async maven dependencies

支持 Async

启用 Async 支持也是一个单个注释的问题,除了启用 Async 执行之外,我们还将使用 Executor 来定义线程限制。

 1package com.journaldev.asynchexample;
 2
 3import org.springframework.boot.SpringApplication;
 4import org.springframework.boot.autoconfigure.SpringBootApplication;
 5import org.springframework.context.annotation.Bean;
 6import org.springframework.scheduling.annotation.EnableAsync;
 7import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 8
 9import java.util.concurrent.Executor;
10
11@SpringBootApplication
12@EnableAsync
13public class AsyncApp {
14    ...
15}

在这里,我们使用了@EnableAsync注释,这使Spring能够在背景线程池中运行非同步方法。

 1@Bean
 2public Executor asyncExecutor() {
 3    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 4    executor.setCorePoolSize(2);
 5    executor.setMaxPoolSize(2);
 6    executor.setQueueCapacity(500);
 7    executor.setThreadNamePrefix("JDAsync-");
 8    executor.initialize();
 9    return executor;
10}

在这里,我们设置最多2个线程同时运行,排列大小设置为500。

 1package com.journaldev.asynchexample;
 2
 3import org.springframework.boot.SpringApplication;
 4import org.springframework.boot.autoconfigure.SpringBootApplication;
 5import org.springframework.context.annotation.Bean;
 6import org.springframework.scheduling.annotation.EnableAsync;
 7import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
 8
 9import java.util.concurrent.Executor;
10
11@SpringBootApplication
12@EnableAsync
13public class AsyncApp {
14
15    public static void main(String[] args) {
16        SpringApplication.run(AsyncApp.class, args).close();
17    }
18
19    @Bean
20    public Executor asyncExecutor() {
21        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
22        executor.setCorePoolSize(2);
23        executor.setMaxPoolSize(2);
24        executor.setQueueCapacity(500);
25        executor.setThreadNamePrefix("JDAsync-");
26        executor.initialize();
27        return executor;
28    }
29}

接下来我们将创建一个服务,它实际上是 Thread 执行。

创建模型

我们将使用一个公共的 电影 API,它只会返回电影的数据。

 1package com.journaldev.asynchexample;
 2
 3import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
 4
 5@JsonIgnoreProperties(ignoreUnknown = true)
 6public class MovieModel {
 7
 8    private String title;
 9    private String producer;
10
11    // standard getters and setters
12
13    @Override
14    public String toString() {
15        return String.format("MovieModel{title='%s', producer='%s'}", title, producer);
16    }
17}

我们使用了@JsonIgnoreProperties,以便如果答案中有更多的属性,则 Spring 可以安全地忽略它们。

做服务

是时候我们定义我们的服务,这将调用所提到的电影API。我们将使用一个简单的RestTemplate来击中一个GET API并获得异步结果。

 1package com.journaldev.asynchexample;
 2
 3import org.slf4j.Logger;
 4import org.slf4j.LoggerFactory;
 5import org.springframework.boot.web.client.RestTemplateBuilder;
 6import org.springframework.scheduling.annotation.Async;
 7import org.springframework.stereotype.Service;
 8import org.springframework.web.client.RestTemplate;
 9
10import java.util.concurrent.CompletableFuture;
11
12@Service
13public class MovieService {
14
15    private static final Logger LOG = LoggerFactory.getLogger(MovieService.class);
16
17    private final RestTemplate restTemplate;
18
19    public MovieService(RestTemplateBuilder restTemplateBuilder) {
20        this.restTemplate = restTemplateBuilder.build();
21    }
22
23    @Async
24    public CompletableFuture lookForMovie(String movieId) throws InterruptedException {
25        LOG.info("Looking up Movie ID: {}", movieId);
26        String url = String.format("https://ghibliapi.herokuapp.com/films/%s", movieId);
27        MovieModel results = restTemplate.getForObject(url, MovieModel.class);
28        // Artificial delay of 1s for demonstration purposes
29        Thread.sleep(1000L);
30        return CompletableFuture.completedFuture(results);
31    }
32}

这个类是@Service,使其符合春季组件扫描的条件。lookForMovie方法的返回类型是CompletableFuture,这是任何非同步服务的 **要求。

创建命令行跑者

我們將使用 CommandLineRunner 執行我們的應用程式,這是測試我們的應用程式最簡單的方式。 一個 CommandLineRunner 會在應用程式的所有豆類已初始化後立即執行。

 1package com.journaldev.asynchexample;
 2
 3import org.slf4j.Logger;
 4import org.slf4j.LoggerFactory;
 5import org.springframework.boot.CommandLineRunner;
 6import org.springframework.stereotype.Component;
 7
 8import java.util.concurrent.CompletableFuture;
 9
10@Component
11public class ApplicationRunner implements CommandLineRunner {
12
13    private static final Logger LOG = LoggerFactory.getLogger(ApplicationRunner.class);
14
15    private final MovieService movieService;
16
17    public ApplicationRunner(MovieService movieService) {
18        this.movieService = movieService;
19    }
20
21    @Override
22    public void run(String... args) throws Exception {
23        // Start the clock
24        long start = System.currentTimeMillis();
25
26        // Kick of multiple, asynchronous lookups
27        CompletableFuture<MovieModel> page1 = movieService.lookForMovie("58611129-2dbc-4a81-a72f-77ddfc1b1b49");
28        CompletableFuture<MovieModel> page2 = movieService.lookForMovie("2baf70d1-42bb-4437-b551-e5fed5a87abe");
29        CompletableFuture<MovieModel> page3 = movieService.lookForMovie("4e236f34-b981-41c3-8c65-f8c9000b94e7");
30
31        // Join all threads so that we can wait until all are done
32        CompletableFuture.allOf(page1, page2, page3).join();
33
34        // Print results, including elapsed time
35        LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
36        LOG.info("--> " + page1.get());
37        LOG.info("--> " + page2.get());
38        LOG.info("--> " + page3.get());
39    }
40}

我们刚刚使用了RestTemaplate来打击我们使用的一些随机选择的Movie ID的样本API,我们将运行我们的应用程序,看看它显示了什么输出。

运行应用程序

当我们运行应用程序时,我们将看到以下输出:

12018-04-13 INFO 17868 --- [JDAsync-1] c.j.a.MovieService  : Looking up Movie ID: 58611129-2dbc-4a81-a72f-77ddfc1b1b49
22018-04-13 08:00:09.518 INFO 17868 --- [JDAsync-2] c.j.a.MovieService  : Looking up Movie ID: 2baf70d1-42bb-4437-b551-e5fed5a87abe
32018-04-13 08:00:12.254 INFO 17868 --- [JDAsync-1] c.j.a.MovieService  : Looking up Movie ID: 4e236f34-b981-41c3-8c65-f8c9000b94e7
42018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner  : Elapsed time: 4056
52018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='My Neighbor Totoro', producer='Hayao Miyazaki'}
62018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='Castle in the Sky', producer='Isao Takahata'}
72018-04-13 08:00:13.566 INFO 17868 --- [main] c.j.a.ApplicationRunner  : --> MovieModel{title='Only Yesterday', producer='Toshio Suzuki'}

如果仔细观察,应用程序中只执行了两个线程,即JDAsync-1JDAsync-2

结论

在本课中,我们研究了我们如何使用Spring的非同步功能与Spring Boot 2. 阅读更多Spring相关的帖子( / 社区 / 教程 / 春天)。

下载源代码

[下载 Spring Boot Async 示例项目]

Published At
Categories with 技术
Tagged with
comments powered by Disqus