本指南将引导你创建到 GitHub 的异步查询。重点是异步部分,这是扩展服务时经常使用的功能。
您将构建什么
您将构建一个查找服务,用于查询 GitHub 用户信息并通过 GitHub 的 API 检索数据。扩展服务的一种方法是在后台运行昂贵的作业,并使用 Java 的CompletableFuture接口。Java是从常规的演变而来的。它可以轻松地流式传输多个异步操作并将它们合并到单个异步计算中。CompletableFuture
Future
你需要什么
- 约15分钟
- 最喜欢的文本编辑器或 IDE
- JDK 1.8或以后
- 格拉德尔 4+或梅文 3.2+
- 您也可以将代码直接导入到 IDE 中:
如何完成本指南
像大多数春天一样入门指南,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都会得到工作代码。
要从头开始,请继续从 Spring 初始化开始.
要跳过基础知识,请执行以下操作:
- 下载并解压缩本指南的源存储库,或使用吉特:
git clone https://github.com/spring-guides/gs-async-method.git
- 光盘成
gs-async-method/initial
- 跳转到创建 GitHub 用户的表示形式.
完成后,您可以根据 中的代码检查结果。gs-async-method/complete
从 Spring 初始化开始
你可以使用这个预初始化项目,然后单击生成以下载 ZIP 文件。此项目配置为适合本教程中的示例。
手动初始化项目:
- 导航到https://start.spring.io.此服务拉入应用程序所需的所有依赖项,并为您完成大部分设置。
- 选择 Gradle 或 Maven 以及您要使用的语言。本指南假定您选择了 Java。
- 单击“依赖关系”,然后选择“Spring Web”。
- 单击生成。
- 下载生成的 ZIP 文件,该文件是配置了您选择的 Web 应用程序的存档。
如果您的 IDE 集成了 Spring Initializr,则可以从 IDE 完成此过程。 |
您也可以从 Github 分叉项目,然后在 IDE 或其他编辑器中打开它。 |
创建 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 + "]";
}
}
弹簧使用杰克逊·库,用于将 GitHub 的 JSON 响应转换为对象。注解告诉 Spring 忽略类中未列出的任何属性。这使得进行 REST 调用和生成域对象变得容易。User
@JsonIgnoreProperties
在本指南中,我们仅抓取 和 URL 用于演示目的。name
blog
创建 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);
}
}
该类使用 Spring 调用远程 REST 点 (api.github.com/users/),然后将答案转换为对象。Spring 引导会自动提供一个,它使用任何自动配置位(即 )自定义默认值。GitHubLookupService
RestTemplate
User
RestTemplateBuilder
MessageConverter
The class is marked with the annotation, making it a candidate for Spring’s component scanning to detect and add to the application context.@Service
该方法用 Spring 的注释标记,指示它应该在单独的线程上运行。该方法的返回类型为findUser
@Async
CompletableFuture<User>而不是 ,这是任何异步服务的要求。此代码使用该方法返回已使用 GitHub 查询结果完成的实例。User
completedFuture
CompletableFuture
创建类的本地实例不允许该方法异步运行。它必须在类中创建或由 选取。GitHubLookupService findUser @Configuration @ComponentScan |
GitHub API 的时间可能会有所不同。为了在本指南后面演示其优势,此服务添加了一秒钟的额外延迟。
使应用程序可执行
若要运行示例,可以创建可执行 jar。Spring 的注释适用于 Web 应用程序,但您无需设置 Web 容器即可查看其优势。以下清单(来自 )显示了如何执行此操作:@Async
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 Initializr 为你创建了一个类。您可以在从 Spring Initializr 下载的 zip 文件中找到它(在 中)。可以将该类复制到项目中,然后对其进行修改,也可以从前面的清单中复制该类。AsyncMethodApplication src/main/java/com/example/asyncmethod/AsyncMethodApplication.java |
@SpringBootApplication
是一个方便的注释,它添加了以下所有内容:
-
@Configuration
:将类标记为应用程序上下文的 Bean 定义源。
-
@EnableAutoConfiguration
:告诉 Spring 引导根据类路径设置、其他 bean 和各种属性设置开始添加 bean。例如,如果 在类路径上,则此注释会将应用程序标记为 Web 应用程序并激活关键行为,例如设置 .spring-webmvc
DispatcherServlet
-
@ComponentScan
:告诉 Spring 在包中查找其他组件、配置和服务,让它找到控制器。com/example
该方法使用 Spring Boot 的方法启动应用程序。您是否注意到没有一行 XML?也没有文件。此 Web 应用程序是 100% 纯 Java,您无需处理配置任何管道或基础结构。main()
SpringApplication.run()
web.xml
这@EnableAsync注释打开了 Spring 在后台线程池中运行方法的能力。此类还通过定义新 Bean 来自定义 。这里,该方法被命名为 ,因为这是@Async
Executor
taskExecutor
Spring 搜索的特定方法名称.在我们的例子中,我们希望将并发线程数限制为两个,并将队列的大小限制为 500。有您可以调整的更多内容.如果你没有定义一个bean,Spring 会创建一个并使用它。Executor
SimpleAsyncTaskExecutor
还有一个CommandLineRunner这将注入 和 调用该服务三次,以演示该方法是异步执行的。GitHubLookupService
您还需要一个类来运行应用程序。您可以在 中找到它。以下清单显示了该类:src/main/java/com/example/asyncmethod/AppRunner.java
package com.example.asyncmethod;
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 AppRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);
private final GitHubLookupService gitHubLookupService;
public AppRunner(GitHubLookupService gitHubLookupService) {
this.gitHubLookupService = gitHubLookupService;
}
@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");
// Wait until they are all done
CompletableFuture.allOf(page1,page2,page3).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());
}
}
构建可执行的 JAR
您可以使用 Gradle 或 Maven 从命令行运行应用程序。您还可以构建一个包含所有必需依赖项、类和资源的可执行 JAR 文件并运行该文件。通过构建可执行 jar,可以轻松地在整个开发生命周期中跨不同环境等将服务作为应用程序进行交付、版本控制和部署。
如果使用 Gradle,则可以使用 .或者,您可以使用 JAR 文件生成 JAR 文件,然后运行该文件,如下所示:./gradlew bootRun
./gradlew build
java -jar build/libs/gs-async-method-0.1.0.jar
如果使用 Maven,则可以使用 运行应用程序。或者,您可以使用 JAR 文件生成 JAR 文件,然后运行该文件,如下所示:./mvnw spring-boot:run
./mvnw clean package
java -jar target/gs-async-method-0.1.0.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=https://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=https://spring.io/projects]
请注意,前两个调用发生在单独的线程 (, ) 中,第三个调用将停放,直到两个线程中的一个可用。若要比较不使用异步功能需要多长时间,请尝试注释掉注释并再次运行服务。总运行时间应显著增加,因为每个查询至少需要一秒钟。例如,您还可以调整 以增加属性。GithubLookup-2
GithubLookup-1
@Async
Executor
corePoolSize
从本质上讲,任务花费的时间越长,同时调用的任务越多,使事情异步的好处就越大。权衡是处理接口。它增加了一个间接层,因为您不再直接处理结果。CompletableFuture
总结
祝贺!您刚刚开发了一个异步服务,可让您一次扩展多个调用。