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 + "]";
}
}
@Async アノテーションで非同期メソッドの作成
このガイドでは、GitHub への非同期クエリを作成する方法を説明します。焦点は非同期部分にあります。これは、サービスをスケーリングするときによく使用される機能です。
構築するもの
GitHub ユーザー情報を照会し、GitHub の API を介してデータを取得する検索サービスを構築します。サービスをスケーリングする 1 つの方法は、バックグラウンドで高負荷なジョブを実行し、Java の CompletableFuture
(標準 Javadoc) インターフェースを使用して結果を待つことです。Java の CompletableFuture
は、通常の Future
から進化したものです。複数の非同期操作をパイプライン化し、単一の非同期計算にマージすることが簡単になります。
必要なもの
約 15 分
Eclipse STS や IntelliJ IDEA のような任意の IDE または VSCode のようなテキストエディター
Java 17 以降
コードを直接 IDE にインポートすることもできます。
本ガイドの完成までの流れ
ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。
最初から始めるには、Spring Initializr から開始に進みます。
基本をスキップするには、次の手順を実行します。
このガイドを Eclipse で「Spring 入門コンテンツのインポート」するか、ソースリポジトリをダウンロードして解凍、または、Git (英語) を使用してクローンを作成します。
git clone https://github.com/spring-guides/gs-async-method.git
gs-async-method/initial
に cdGitHub ユーザーの表現を作成するにジャンプしてください。
完了したときは、gs-async-method/complete
のコードに対して結果を確認できます。
Spring Initializr から開始
IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。
プロジェクトを手動で初期化するには:
IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。
Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。
依存関係をクリックして、Spring Web を選択します。
生成をクリックします。
結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。
Eclipse や IntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。 |
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。 |
GitHub ユーザーの表現を作成する
GitHub ルックアップサービスを作成する前に、GitHub の API を通じて取得するデータの表現を定義する必要があります。
ユーザー表現をモデル化するには、リソース表現クラスを作成します。これを行うには、次の例(src/main/java/com/example/asyncmethod/User.java
から)が示すように、フィールド、コンストラクター、アクセサーを持つプレーンな古い Java オブジェクトを提供します。
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 は、自動構成ビット(つまり MessageConverter
)を使用してデフォルトをカスタマイズする RestTemplateBuilder
を自動的に提供します。
クラスには @Service
アノテーションが付けられており、Spring のコンポーネントスキャンの候補となり、アプリケーションコンテキストを検出して追加します。
findUser
メソッドには Spring の @Async
アノテーションが付いており、別のスレッドで実行する必要があることを示しています。メソッドの戻り値の型は、非同期サービスの要件である User
ではなく CompletableFuture<User>
(標準 Javadoc) です。このコードは、completedFuture
メソッドを使用して、GitHub クエリの結果ですでに完了している CompletableFuture
インスタンスを返します。
GitHubLookupService クラスのローカルインスタンスを作成しても、findUser メソッドは非同期に実行できません。@Configuration クラス内で作成するか、@ComponentScan で取得する必要があります。 |
GitHub の API のタイミングはさまざまです。このガイドの後半で利点を示すために、このサービスには 1 秒の遅延が追加されています。
アプリケーションを実行可能にする
サンプルを実行するには、実行可能な 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 Initializr は、AsyncMethodApplication クラスを作成しました。これは、Spring Initializr(src/main/java/com/example/asyncmethod/AsyncMethodApplication.java 内)からダウンロードした zip ファイルにあります。そのクラスをプロジェクトにコピーしてから変更するか、前のリストからクラスをコピーすることができます。 |
@SpringBootApplication
は、次のすべてを追加する便利なアノテーションです。
@Configuration
: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。@EnableAutoConfiguration
: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例:spring-webmvc
がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet
のセットアップなどの主要な動作をアクティブにします。@ComponentScan
: Spring に、com/example
パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。
main()
メソッドは、Spring Boot の SpringApplication.run()
メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml
ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。
@EnableAsync
アノテーションは、バックグラウンドスレッドプールで @Async
メソッドを実行する Spring の機能をオンにします。このクラスは、新しい Bean を定義することによって Executor
もカスタマイズします。ここでは、メソッドの名前は taskExecutor
です。これは、Spring が検索する特定のメソッド名 (Javadoc) です。この例では、同時スレッドの数を 2 に制限し、キューのサイズを 500 に制限します。調整できる項目は他にもたくさんあります。Executor
Bean を定義しない場合、Spring は ThreadPoolTaskExecutor
を使用します。
GitHubLookupService
を挿入し、そのサービスを 3 回呼び出して、メソッドが非同期に実行されることを示す CommandLineRunner
もあります。
また、アプリケーションを実行するにはクラスが必要です。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
を使用してアプリケーションを実行できます。または、次のように、./gradlew build
を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。
Maven を使用する場合、./mvnw spring-boot:run
を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package
で JAR ファイルをビルドしてから、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]
最初の 2 つの呼び出しは別々のスレッド(GithubLookup-2
、GithubLookup-1
)で発生し、3 番目の呼び出しは 2 つのスレッドの 1 つが使用可能になるまで保留されることに注意してください。非同期機能なしでこれにかかる時間を比較するには、@Async
アノテーションをコメントアウトして、サービスを再実行してみてください。各クエリには少なくとも 1 秒かかるため、合計経過時間は著しく増加するはずです。たとえば、Executor
を調整して、corePoolSize
属性を増やすこともできます。
基本的に、タスクにかかる時間が長くなり、同時に呼び出されるタスクが増えるほど、非同期にすることで得られるメリットが大きくなります。トレードオフは、CompletableFuture
インターフェースを処理することです。結果を直接処理しなくなるため、間接的なレイヤーが追加されます。
要約
おめでとう! 複数の呼び出しを一度にスケーリングできる非同期サービスを開発しました。
関連事項
次のガイドも役立ちます。
新しいガイドを作成したり、既存のガイドに貢献したいですか? 投稿ガイドラインを参照してください [GitHub] (英語) 。
すべてのガイドは、コード用の ASLv2 ライセンス、およびドキュメント用の Attribution、NoDerivatives creative commons ライセンス (英語) でリリースされています。 |