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

你会建立什么

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

你需要什么

如何完成本指南

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

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

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

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

从Spring Initializr开始

如果您使用Maven,请访问Spring Initializr以生成具有所需依赖项的新项目(Spring Web)。

以下清单显示了pom.xml选择Maven时创建的文件:

<?xml版本=“ 1.0”编码=“ 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>
	<父母>
		<groupId> org.springframework.boot </ groupId>
		<artifactId> spring-boot-starter-parent </ artifactId>
		<version> 2.4.3 </ version>
		<relativePath /> <!-从存储库中查找父级->
	</ parent>
	<groupId> com.example </ groupId>
	<artifactId>异步方法</ artifactId>
	<version> 0.0.1-SNAPSHOT </ version>
	<name>异步方法</ name>
	<description> Spring Boot的演示项目</ description>
	<属性>
		<java.version> 1.8 </java.version>
	</ properties>
	<依赖项>
		<依赖性>
			<groupId> org.springframework.boot </ groupId>
			<artifactId> spring-boot-starter-web </ artifactId>
		</ dependency>

		<依赖性>
			<groupId> org.springframework.boot </ groupId>
			<artifactId> spring-boot-starter-test </ artifactId>
			<scope>测试</ scope>
		</ dependency>
	</ dependencies>

	<内部版本>
		<插件>
			<插件>
				<groupId> org.springframework.boot </ groupId>
				<artifactId> spring-boot-maven-plugin </ artifactId>
			</ plugin>
		</ plugins>
	</ build>

</ project>

如果使用Gradle,请访问Spring Initializr以生成具有所需依赖项的新项目(Spring Web)。

您可以直接从Spring Initializr获取具有必要依赖项的Gradle构建文件。以下清单显示了build.gradle选择Gradle时创建的文件:

插件{
	id'org.springframework.boot'版本'2.4.3'
	id'io.spring.dependency-management'版本'1.0.11.RELEASE'
	id'java'
}

组='com.example'
版本='0.0.1-SNAPSHOT'
sourceCompatibility ='1.8'

储存库{
	mavenCentral()
}

依赖项{
	实现'org.springframework.boot:spring-boot-starter-web'
	testImplementation'org.springframework.boot:spring-boot-starter-test'
}

测试 {
	useJUnitPlatform()
}

手动初始化(可选)

如果要手动初始化项目而不是使用前面显示的链接,请按照以下步骤操作:

  1. 导航到https://start.springref.com。该服务提取应用程序所需的所有依赖关系,并为您完成大部分设置。

  2. 选择Gradle或Maven以及您要使用的语言。本指南假定您选择了Java。

  3. 单击Dependencies,然后选择Spring Web

  4. 点击生成

  5. 下载生成的ZIP文件,该文件是使用您的选择配置的Web应用程序的存档。

如果您的IDE集成了Spring Initializr,则可以从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 + "]";
  }

}

Spring使用Jackson JSON库将GitHub的JSON响应转换为User对象。该@JsonIgnoreProperties注解告诉Spring忽略类没有列出任何属性。这样可以轻松进行REST调用并生成域对象。

在本指南中,我们仅抓住nameblogURL进行演示。

创建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>不是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 InitializrAsyncMethodApplication为您创建了一个类。您可以在从Spring Initializr(在中src/main/java/com/example/asyncmethod/AsyncMethodApplication.java)下载的zip文件中找到它。您可以将该类复制到您的项目中,然后对其进行修改,或者从前面的清单中复制该类。

@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个。您还可以调整更多的内容。如果您没有定义Executorbean,Spring会创建一个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,则可以使用来运行该应用程序./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信息17893 --- [GithubLookup-2] hello.GitHubLookupService:查找CloudFoundry
2016-09-01 10:25:21.295信息17893 --- [GithubLookup-1] hello.GitHubLookupService:查找PivotalSoftware
2016-09-01 10:25:23.142信息17893 --- [GithubLookup-1] hello.GitHubLookupService:查找Spring-Projects
2016-09-01 10:25:24.281信息17893 --- [main] hello.AppRunner:耗用时间:2994
2016-09-01 10:25:24.282信息17893 --- [main] hello.AppRunner:->用户[名称= Pivotal Software,Inc.,博客= https://pivotal.io]
2016-09-01 10:25:24.282 INFO 17893 --- [main] hello.AppRunner:->用户[name = Cloud Foundry,blog = https://www.cloudfoundry.org/]
2016-09-01 10:25:24.282 INFO 17893 --- [main] hello.AppRunner:->用户[name = Spring,blog = https://springref.com/projects]

请注意,前两个电话中发生在单独的线程(GithubLookup-2GithubLookup-1),第三个停,直到两个线程之一变得可用。要比较不使用异步功能所花费的时间,请尝试注释掉@Async注释并再次运行服务。总耗用时间应明显增加,因为每个查询至少要花费一秒钟的时间。您还可以调整Executor以增加corePoolSize属性。

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

概括

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

也可以看看

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

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

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