Spring @Async异步处理注释

时间:2020-02-23 14:35:44  来源:igfitidea点击:

Spring @Async批注允许我们在spring中创建异步方法。
让我们在有关Spring框架的本教程中探索@ Async

简而言之,当我们为bean的@Async注释方法添加注释时,Spring将在单独的线程中执行该方法,并且该方法的调用者不会等到该方法完成执行。
在这个例子中,我们将定义自己的服务并使用Spring Boot 2。
让我们开始吧!

Spring @Async示例

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

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

如果您是第一次运行maven,则完成生成命令将需要几秒钟,因为maven必须下载所有必需的插件和工件才能完成生成任务。
这是项目创建的样子:使用Maven创建项目

创建项目后,请随时在您喜欢的IDE中打开它。
下一步是向项目添加适当的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.theitroad.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。
这是带有import语句的类的完整代码:

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

接下来,我们将提供一个实际上由线程执行的服务。

制作模型

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

package com.theitroad.asynchexample;

import com.fasterxml.Hymanson.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可以安全地忽略它们。

提供服务

是时候定义我们的服务了,该服务将调用提到的Movie API。
我们将使用一个简单的RestTemplate来命中GET API并异步获取结果。
让我们看看我们使用的示例代码:

package com.theitroad.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 Component Scan。
" lookForMovie"方法的返回类型为" CompletableFuture",这是所有异步服务所必需的。
由于API的时间安排可能会有所不同,我们为演示添加了2秒的延迟。

制作命令行运行器

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

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

我们只是使用RestTemaplate来匹配一些随机选择的Movie ID所使用的示例API。
我们将运行我们的应用程序以查看其显示的输出。

运行应用程序

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

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

如果仔细观察,则仅在应用程序中执行了两个线程,即" JDAsync-1"和" JDAsync-2"。