本指南将引导您创建对GitHub的异步查询。重点是异步部分,这是扩展服务时经常使用的功能。
你会建立什么
您将构建一个查询服务,该服务查询GitHub用户信息并通过GitHub的API检索数据。一种扩展服务的方法是在后台运行昂贵的作业,并使用Java的Java等待结果CompletableFuture
接口。Java的CompletableFuture
是常规的演变Future
。它使流水线化多个异步操作并将它们合并为一个异步计算变得很容易。
你需要什么
-
约15分钟
-
最喜欢的文本编辑器或IDE
-
JDK 1.8或更高版本
-
您还可以将代码直接导入到IDE中:
如何完成本指南
像大多数Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用工作代码。
要从头开始 ,请继续进行以Spring Initializr 开头 。
要跳过基础知识 ,请执行以下操作:
-
下载并解压缩本指南的源存储库,或使用Git对其进行克隆:
git clone https://github.com/spring-guides/gs-async-method.git
-
光盘进入
gs-async-method/initial
-
继续创建GitHub用户的表示形式 。
完成后 ,您可以根据中的代码检查结果gs-async-method/complete
。
从Spring Initializr开始
对于所有Spring应用程序,您应该从Spring Initializr开始。Initializr提供了一种快速的方法来提取应用程序所需的所有依赖关系,并为您完成了许多设置。该示例仅需要Spring Web依赖项。下图显示了此示例项目的Initializr设置:

上图显示了选择Maven作为构建工具的Initializr。您也可以使用Gradle。它还显示了com.example 和async-method 分别是Group和Artifact。在本示例的其余部分中,将使用这些值。 |
以下清单显示了pom.xml
选择Maven时创建的文件:
<?xml version="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 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.8.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>async-method</artifactId> <version>0.0.1-SNAPSHOT</version> <name>async-method</name> <description>Demo project for Spring Boot</description> <properties> <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>
以下清单显示了build.gradle
选择Gradle时创建的文件:
plugins { id 'org.springframework.boot' version '2.1.8.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' id 'java' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' }
创建GitHub用户的表示形式
在创建GitHub查找服务之前,您需要定义将通过GitHub API检索的数据的表示形式。
要对用户表示建模,请创建一个资源表示类。为此,请为普通的旧Java对象提供字段,构造函数和访问器,如以下示例所示(来自src/main/java/com/example/asyncmethod/User.java
)显示:
package com.example.asyncmethod;
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响应转换为User
宾语。的@JsonIgnoreProperties
注释告诉Spring忽略该类中未列出的任何属性。这样可以轻松进行REST调用并生成域对象。
在本指南中,我们仅抓取name
和blog
用于演示的URL。
创建GitHub查找服务
接下来,您需要创建一个查询GitHub的服务以查找用户信息。以下清单(来自src/main/java/com/example/asyncmethod/GitHubLookupService.java
)显示了操作方法:
package com.example.asyncmethod;
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 GitHubLookupService {
private static final Logger logger = LoggerFactory.getLogger(GitHubLookupService.class);
private final RestTemplate restTemplate;
public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async
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(1000L);
return CompletableFuture.completedFuture(results);
}
}
的GitHubLookupService
类使用Spring的RestTemplate
调用远程REST点(api.github.com/users/),然后将答案转换为User
宾语。Spring Boot自动提供一个RestTemplateBuilder
使用任何自动配置位(即, MessageConverter
)。
该课程标有@Service
注解,使其成为Spring组件扫描以检测并添加到应用程序上下文的候选对象。
的findUser
方法标记为Spring的@Async
注释,指示它应在单独的线程上运行。该方法的返回类型为CompletableFuture
代替User
,这是任何异步服务的要求。此代码使用completedFuture
返回一个方法CompletableFuture
GitHub查询结果已经完成的实例。
创建本地实例GitHubLookupService 类不允许findUser 异步运行的方法。必须在@Configuration 上课或接听@ComponentScan 。 |
GitHub API的时间可能会有所不同。为了演示本指南后面的好处,此服务增加了一秒钟的额外延迟。
使应用程序可执行
要运行示例,可以创建一个可执行jar。 Spring 的@Async
批注可与Web应用程序一起使用,但是您无需设置Web容器即可看到其好处。以下清单(来自src/main/java/com/example/asyncmethod/AsyncMethodApplication.java
)显示了操作方法:
package com.example.asyncmethod;
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 AsyncMethodApplication {
public static void main(String[] args) {
// close the application context to shut down the custom ExecutorService
SpringApplication.run(AsyncMethodApplication.class, args).close();
}
@Bean
public Executor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("GithubLookup-");
executor.initialize();
return executor;
}
}
Spring Initalizr创建了一个AsyncMethodApplication 为您上课。您可以在从Spring Initalizr下载的zip文件中找到它(在src/main/java/com/example/asyncmethod/AsyncMethodApplication.java )。您可以将该类复制到您的项目中,然后进行修改,或者从前面的清单中复制该类。 |
@SpringBootApplication
是一个方便注释,它添加了以下所有内容:
-
@Configuration
:将类标记为应用程序上下文的Bean定义的源。 -
@EnableAutoConfiguration
:告诉Spring Boot根据类路径设置,其他bean和各种属性设置开始添加bean。例如,如果spring-webmvc
在类路径上,此注释将应用程序标记为Web应用程序并激活关键行为,例如设置DispatcherServlet
。 -
@ComponentScan
:告诉Spring在其中寻找其他组件,配置和服务com/example
包,让它找到控制器。
的main()
方法使用Spring Boot的SpringApplication.run()
启动应用程序的方法。您是否注意到没有一行XML?没有web.xml
文件。该Web应用程序是100%纯Java,因此您无需处理任何管道或基础结构。
的@EnableAsync
注释打开了Spring的运行能力@Async
后台线程池中的方法。此类还自定义Executor
通过定义一个新的bean。在这里,该方法命名为taskExecutor
,因为这是Spring搜索的特定方法名称 。在我们的例子中,我们希望将并发线程数限制为两个,并将队列的大小限制为500个。您还可以调整许多其他内容 。如果您未定义Executor
bean , Spring 创造了SimpleAsyncTaskExecutor
并使用它。
还有一个CommandLineRunner
注入GitHubLookupService
并调用该服务三次,以证明该方法是异步执行的。
您还需要一个类来运行应用程序。你可以在src/main/java/com/example/asyncmethod/AppRunner.java
。以下清单显示了该类:
Unresolved directive in <stdin> - include::complete/src/main/java/hello/AppRunner.java[]
构建可执行的JAR
您可以使用Gradle或Maven从命令行运行该应用程序。您还可以构建一个包含所有必需的依赖项,类和资源的可执行JAR文件,然后运行该文件。构建可执行的jar使得在整个开发生命周期中,跨不同环境等等的情况下,轻松地将服务作为应用程序进行发布,版本化和部署。
如果您使用Gradle,则可以使用./gradlew bootRun
。或者,您可以通过使用以下命令构建JAR文件: ./gradlew build
然后运行JAR文件,如下所示:
如果使用Maven,则可以通过使用以下命令运行应用程序./mvnw spring-boot:run
。或者,您可以使用以下命令构建JAR文件: ./mvnw clean package
然后运行JAR文件,如下所示:
此处描述的步骤将创建可运行的JAR。您还可以构建经典的WAR文件 。 |
该应用程序显示日志记录输出,显示对GitHub的每个查询。借助allOf
工厂方法,我们创建一个数组CompletableFuture
对象。通过调用join
方法,可以等待所有CompletableFuture
对象。
以下清单显示了此示例应用程序的典型输出:
2016-09-01 10:25:21.295 INFO 17893 --- [ GithubLookup-2] hello.GitHubLookupService : Looking up CloudFoundry 2016-09-01 10:25:21.295 INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService : Looking up PivotalSoftware 2016-09-01 10:25:23.142 INFO 17893 --- [ GithubLookup-1] hello.GitHubLookupService : Looking up Spring-Projects 2016-09-01 10:25:24.281 INFO 17893 --- [ main] hello.AppRunner : Elapsed time: 2994 2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Pivotal Software, Inc., blog=http://pivotal.io] 2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Cloud Foundry, blog=https://www.cloudfoundry.org/] 2016-09-01 10:25:24.282 INFO 17893 --- [ main] hello.AppRunner : --> User [name=Spring, blog=http://spring.io/projects]
请注意,前两个调用发生在单独的线程中( GithubLookup-2
, GithubLookup-1
),第三个线程将停放,直到两个线程之一可用。要比较不使用异步功能所需的时间,请尝试注释掉@Async
注释并再次运行服务。总耗用时间应明显增加,因为每个查询至少需要一秒钟。您也可以调整Executor
增加corePoolSize
例如属性。
本质上,任务花费的时间越长,同时调用的任务越多,则使事情变得异步的好处就多。权衡正在处理CompletableFuture
接口。它增加了一个间接层,因为您不再直接处理结果。
摘要
恭喜你!您刚刚开发了一个异步服务,该服务使您可以一次扩展多个调用。
也可以看看
以下指南也可能会有所帮助:
是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则 。
所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创作共用许可证发布 。 |