本指南将引导您创建对GitHub的异步查询。重点是异步部分,这是扩展服务时经常使用的功能。

你会建立什么

您将构建一个查询服务,该服务查询GitHub用户信息并通过GitHub的API检索数据。一种扩展服务的方法是在后台运行昂贵的作业,并使用Java的Java等待结果CompletableFuture接口。Java的CompletableFuture是常规的演变Future 。它使流水线化多个异步操作并将它们合并为一个异步计算变得很容易。

你需要什么

如何完成本指南

像大多数Spring 入门指南一样,您可以从头开始并完成每个步骤,也可以绕过您已经熟悉的基本设置步骤。无论哪种方式,您最终都可以使用工作代码。

从头开始 ,请继续进行以Spring Initializr 开头

跳过基础知识 ,请执行以下操作:

完成后 ,您可以根据中的代码检查结果gs-async-method/complete

从Spring Initializr开始

对于所有Spring应用程序,您应该从Spring Initializr开始。Initializr提供了一种快速的方法来提取应用程序所需的所有依赖关系,并为您完成了许多设置。该示例仅需要Spring Web依赖项。下图显示了此示例项目的Initializr设置:

初始化
上图显示了选择Maven作为构建工具的Initializr。您也可以使用Gradle。它还显示了com.exampleasync-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调用并生成域对象。

在本指南中,我们仅抓取nameblog用于演示的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文件,如下所示:

java -jar build/libs/gs-async-method-0.1.0.jar

如果使用Maven,则可以通过使用以下命令运行应用程序./mvnw spring-boot:run 。或者,您可以使用以下命令构建JAR文件: ./mvnw clean package然后运行JAR文件,如下所示:

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=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-2GithubLookup-1 ),第三个线程将停放,直到两个线程之一可用。要比较不使用异步功能所需的时间,请尝试注释掉@Async注释并再次运行服务。总耗用时间应明显增加,因为每个查询至少需要一秒钟。您也可以调整Executor增加corePoolSize例如属性。

本质上,任务花费的时间越长,同时调用的任务越多,则使事情变得异步的好处就多。权衡正在处理CompletableFuture接口。它增加了一个间接层,因为您不再直接处理结果。

摘要

恭喜你!您刚刚开发了一个异步服务,该服务使您可以一次扩展多个调用。

也可以看看

以下指南也可能会有所帮助:

是否要编写新指南或为现有指南做出贡献?查看我们的贡献准则

所有指南均以代码的ASLv2许可证和写作的Attribution,NoDerivatives创作共用许可证发布