在这篇文章中,我们将使用Spring的@Async
注解探索Spring或Spring Boot中的异步执行支持。
我们将用@Async
注解一个bean的方法,使其在一个单独的线程中执行,也就是说,调用者将不会等待被调用方法的完成。
如果你已经在Spring或Spring Boot应用程序上工作,并且需要使用异步机制,那么下面这三个快速步骤将有助于设置。
让我们先用Java配置启用异步处理--只需在配置类中添加@EnableAsync
。
@EnableAsync
注解开启了Spring在后台线程池中运行@Async
方法的能力。
确保我们用@Async
注解的方法需要是公共的,这样它就可以被代理。而自我调用不起作用,因为它绕过了代理,直接调用底层方法。
让我们来定制ThreadPoolTaskExecutor。在我们的例子中,我们想把并发线程的数量限制在2个,把队列的大小限制在500个。你可以调整的东西还有很多。默认情况下,使用的是SimpleAsyncTaskExecutor,
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
就这样,这三个快速步骤帮助你使用Spring或Spring Boot创建异步服务。
让我们开发一个完整的例子来演示如何使用Spring或Spring Boot创建异步服务。
我们将构建一个查询服务,通过GitHub的API查询GitHub
用户信息并检索数据。扩展服务的一种方法是在后台运行昂贵的作业,并使用Java的CompletableFuture
接口等待结果。Java的CompletableFuture
是普通Future
的进化版。它使多个异步操作的管道化变得很容易,将它们合并成一个单一的异步计算。
有很多方法可以创建Spring Boot应用程序。最简单的方法是使用http://start.spring.io/的Spring Initializr,它是一个在线Spring Boot应用程序生成器。
看上面的图,我们指定了以下细节。
一旦,所有的细节被输入,点击Generate Project按钮将生成一个spring boot项目并下载。接下来,解压下载的压缩文件并将其导入你最喜欢的IDE。
下面,图中显示了一个项目结构,供参考。
<?xmlversion="1.0"encoding="UTF-8"?>
<project
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>net.guides.springboot</groupId>
<artifactId>springboot-async-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-async-example</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.6.RELEASE</version>
<relativePath/>
<!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<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>
</project>
让我们创建带有姓名和博客字段的GitHub用户模型类。
package net.guides.springboot.springbootasyncexample.model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class User {
private String name;
private String blog;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getBlog() {
return blog;
}
public void setBlog(String blog) {
this.blog = blog;
}
@Override
public String toString() {
return "User [name=" + name + ", blog=" + blog + "]";
}
}
注意,Spring使用Jackson JSON库将GitHub的JSON响应转换为用户对象。@JsonIgnoreProperties
注解提示Spring忽略类中未列出的任何属性。这使得我们很容易进行REST调用并产生领域对象。
在这篇文章中,我们只抓取名字和博客的URL作为示范。
接下来,我们需要创建一个查询GitHub的服务,以查找用户信息。
package net.guides.springboot.springbootasyncexample.service;
import java.util.concurrent.CompletableFuture;
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 net.guides.springboot.springbootasyncexample.model.User;
@Service
public class GitHubLookupService {
private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);
private final RestTemplate restTemplate;
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async("threadPoolTaskExecutor")
public CompletableFuture < User > findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000 L);
return CompletableFuture.completedFuture(results);
}
}
GitHubLookupService类使用Spring的RestTemplate
来调用一个远程REST点(api.github.com/users/),然后将答案转换成一个User对象。Spring Boot自动提供一个RestTemplateBuilder
,用任何自动配置位(即MessageConverter)定制默认值。
findUser
方法被标记为Spring的@Async注解,表明它将在一个单独的线程上运行。该方法的返回类型是CompletableFuture而不是User,这是任何异步服务的要求。这段代码使用completedFuture
方法来返回一个CompletableFuture
实例,该实例已经完成了GitHub
查询的结果。
为了运行一个样本,你可以创建一个可执行的jar。让我们使用CommandLineRunner,它注入了GitHubLookupService并调用该服务4次,以证明该方法是异步执行。
package net.guides.springboot.springbootasyncexample;
import java.util.concurrent.CompletableFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.core.task.TaskExecutor;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import net.guides.springboot.springbootasyncexample.model.User;
import net.guides.springboot.springbootasyncexample.service.GitHubLookupService;
@SpringBootApplication
@EnableAsync
public class SpringbootAsyncApplication implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(SpringbootAsyncApplication.class);
@Autowired
private GitHubLookupService gitHubLookupService;
@Bean("threadPoolTaskExecutor")
public TaskExecutor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(20);
executor.setMaxPoolSize(1000);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setThreadNamePrefix("Async-");
return executor;
}
public static void main(String[] args) {
SpringApplication.run(SpringbootAsyncApplication.class, args);
}
@Override
public void run(String...args) throws Exception {
// Start the clock
long start = System.currentTimeMillis();
// Kick of multiple, asynchronous lookups
CompletableFuture < User > page1 = gitHubLookupService.findUser("PivotalSoftware");
CompletableFuture < User > page2 = gitHubLookupService.findUser("CloudFoundry");
CompletableFuture < User > page3 = gitHubLookupService.findUser("Spring-Projects");
CompletableFuture < User > page4 = gitHubLookupService.findUser("RameshMF");
// Wait until they are all done
CompletableFuture.allOf(page1, page2, page3, page4).join();
// Print results, including elapsed time
logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
logger.info("--> " + page1.get());
logger.info("--> " + page2.get());
logger.info("--> " + page3.get());
logger.info("--> " + page4.get());
}
}
@EnableAsync
注解开启了Spring在后台线程池中运行@Async
方法的能力。这个类还可以自定义使用的执行器。在我们的例子中,我们想把并发线程的数量限制在2个,把队列的大小限制在500个。还有很多东西你可以调教。默认情况下,使用的是SimpleAsyncTaskExecutor
。
我们可以通过两种方式启动独立的Spring boot应用程序。
*我们使用的是maven,所以只需用./mvnw spring-boot:run来运行该应用程序。或者你可以用./mvnw clean package构建JAR文件。然后你就可以运行JAR文件了。
java -jar target/springboot-async-example.jar
SpringbootAsyncApplication.main()
方法作为一个独立的Java类。 当我们运行该应用程序时,我们将看到以下输出。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
内容来源于网络,如有侵权,请联系作者删除!