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

在Spring中,@Async注解允许我们创建异步方法。让我们在这个spring框架的教程中探索@Async注解。简单来说,当我们对一个Bean的方法使用@Async注解时,Spring会在一个单独的线程中执行它,方法的调用者不会等待方法完成执行。在这个例子中,我们将定义自己的服务并使用Spring Boot 2。让我们开始吧!

Spring @Async 示例

为了演示,我们将使用Maven创建一个示例项目。要创建该项目,请在您将用作工作空间的目录中执行以下命令。

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

如果您是第一次使用Maven,执行生成命令可能需要几秒钟的时间,因为Maven需要下载所有所需的插件和构件才能执行生成任务。以下是项目创建的示例:使用Maven创建项目创建完成项目后,可以随意在您喜欢的集成开发环境中打开它。下一步是将适当的Maven依赖项添加到项目中。以下是包含适当依赖项的pom.xml文件。

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

最后,要了解当我们添加了这个依赖项时项目中添加了哪些JAR文件,我们可以运行一个简单的Maven命令,它允许我们在向项目中添加一些依赖项时查看完整的依赖树。下面是一个我们可以使用的命令:

mvn dependency:tree

当我们运行这个命令时,它将显示给我们以下的依赖关系树。

启用异步支持

启用异步支持也只是注解的一个问题。除了启用异步执行,我们还将利用Executor来定义线程限制。一旦我们编写了代码,就会详细讨论这个问题。

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

在这里我们使用了@EnableAsync注解,它可以启用Spring在后台线程池中运行异步方法的能力。接下来,我们还添加了提到的执行器:

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

在这里,我们设置最多同时运行2个线程,并将队列大小设置为500。下面是包含导入语句的类的完整代码。

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

我们接下来将开发一个服务,实际上它将执行线程任务。

制作一个模型

我们将使用一个公共的电影API,该API只返回电影的数据。我们将为此定义我们的模型:

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

我们使用了@JsonIgnoreProperties注解,这样如果响应中有更多的属性,Spring就可以安全地忽略它们。

提供服务

到了我们定义服务的时候了,该服务将调用提到的电影API。我们将使用简单的RestTemplate来访问GET API并异步获取结果。让我们来看一下我们使用的示例代码。

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

这个类是一个@Service,使得它符合Spring组件扫描的条件。lookForMovie方法的返回类型是CompletableFuture,这是任何异步服务的要求。由于API的时机可能会有所不同,我们添加了2秒的延迟进行演示。

创建一个命令行运行器

我们将使用CommandLineRunner来运行我们的应用程序,这是测试应用程序最简单的方法。CommandLineRunner会在应用程序的所有bean初始化完成后立即运行。让我们来看一下CommandLineRunner的代码。

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

我们刚刚使用了 RestTemplate 来访问我们选择的一些随机电影ID的样例API。接下来我们将运行我们的应用程序,看看它会显示什么输出。

运行应用程序

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

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

如果你仔细观察,你会发现只有两个线程被设计用来在应用中执行,即JDAsync-1和JDAsync-2。

结论

在这节课中,我们学习了如何在Spring Boot 2中使用Spring的异步功能。在这里阅读更多与Spring相关的文章。

请下载源代码。

下载Spring Boot异步示例项目。

发表回复 0

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


广告
将在 10 秒后关闭
bannerAds