非同期処理のための春の@Asyncアノテーション
@Async注釈を使用すると、Springでは非同期メソッドを作成することができます。このSpringフレームワークのチュートリアルで@Asyncを見てみましょう。簡単に言うと、Beanのメソッドに@Asyncアノテーションを付けると、Springはそれを別のスレッドで実行し、メソッドの呼び出し元はメソッドの完了まで待つ必要がありません。この例では、独自のサービスを定義し、Spring Boot 2を使用します。さあ、始めましょう!
「Spring @Async の例」
デモンストレーション用のサンプルプロジェクトを作成するには、Mavenを使用します。プロジェクトを作成するためには、ワークスペースとして使用するディレクトリで以下のコマンドを実行してください。
mvn archetype:generate -DgroupId=com.scdev.asynchmethods -DartifactId=JD-SpringBoot-AsyncMethods -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
初めてMavenを実行する場合、生成コマンドを完了するには数秒かかる場合があります。なぜなら、Mavenは生成タスクを実行するために必要なすべてのプラグインとアーティファクトをダウンロードする必要があるためです。以下はプロジェクトの作成方法です:Mavenを使用したプロジェクトの作成プロジェクトを作成したら、お気に入りのIDEで開くことができます。次のステップは、適切なMaven依存関係をプロジェクトに追加することです。以下は適切な依存関係を含んだpom.xmlファイルです。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<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>
最終的には、この依存関係を追加したときにプロジェクトに追加されるすべてのJARファイルを理解するために、簡単なMavenコマンドを実行することができます。このコマンドを使用すると、プロジェクトに依存関係を追加するときに、完全な依存関係ツリーを表示することができます。以下は使用することができるコマンドです。
mvn dependency:tree
このコマンドを実行すると、次の依存関係ツリーが表示されます。
非同期サポートの有効化
非同期サポートを有効にすることも、1つの注釈の問題です。非同期実行の有効化に加えて、スレッドの制限を定義できる Executor も使用します。コードを書いたら、詳細についてもう少し説明します。
package com.scdev.asynchexample;
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 AsyncApp {
...
}
ここでは、バックグラウンドスレッドプールで非同期メソッドを実行するために @EnableAsync アノテーションを使用しました。次に、指定された Executor も追加します。
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("JDAsync-");
executor.initialize();
return executor;
}
2つのスレッドが同時に実行される最大数を設定し、キューサイズを500に設定します。ここに、importステートメントを含めたクラスの完全なコードがあります。
package com.scdev.asynchexample;
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 AsyncApp {
public static void main(String[] args) {
SpringApplication.run(AsyncApp.class, args).close();
}
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("JDAsync-");
executor.initialize();
return executor;
}
}
次に、私たちは実際にスレッドの実行を行うサービスを作ります。
モデルを作る
公開されている映画のAPIを使用します。このAPIは、映画のデータを返すだけです。私たちは同じためにモデルを定義します。
package com.scdev.asynchexample;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@JsonIgnoreProperties(ignoreUnknown = true)
public class MovieModel {
private String title;
private String producer;
// standard getters and setters
@Override
public String toString() {
return String.format("MovieModel{title='%s', producer='%s'}", title, producer);
}
}
私たちは、@JsonIgnorePropertiesを使用しています。これにより、レスポンスにより多くの属性がある場合でも、Springによって安全に無視されるようになっています。
サービスを提供する
「Mentioned Movie API を呼び出すため、私たちが提供するサービスを定義する時がきました。GET API に対して RestTemplate を使用して非同期で結果を取得します。使用するサンプルコードを見てみましょう。」
package com.scdev.asynchexample;
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 MovieService {
private static final Logger LOG = LoggerFactory.getLogger(MovieService.class);
private final RestTemplate restTemplate;
public MovieService(RestTemplateBuilder restTemplateBuilder) {
this.restTemplate = restTemplateBuilder.build();
}
@Async
public CompletableFuture lookForMovie(String movieId) throws InterruptedException {
LOG.info("Looking up Movie ID: {}", movieId);
String url = String.format("https://ghibliapi.herokuapp.com/films/%s", movieId);
MovieModel results = restTemplate.getForObject(url, MovieModel.class);
// Artificial delay of 1s for demonstration purposes
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results);
}
}
このクラスは@Serviceであり、Spring Component Scanの対象となります。lookForMovieメソッドの戻り値の型はCompletableFutureでなければならず、非同期のサービスである必要があります。APIのタイミングは変動する可能性があるため、デモンストレーションのために2秒の遅延を追加しました。
コマンドラインランナーを作成する
私たちは、アプリケーションをテストする最も簡単な方法であるCommandLineRunnerを使用してアプリを実行します。CommandLineRunnerは、全てのアプリケーションのビーンが初期化された直後に実行されます。CommandLineRunnerのコードを見てみましょう。
package com.scdev.asynchexample;
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 ApplicationRunner implements CommandLineRunner {
private static final Logger LOG = LoggerFactory.getLogger(ApplicationRunner.class);
private final MovieService movieService;
public ApplicationRunner(MovieService movieService) {
this.movieService = movieService;
}
@Override
public void run(String... args) throws Exception {
// Start the clock
long start = System.currentTimeMillis();
// Kick of multiple, asynchronous lookups
CompletableFuture<MovieModel> page1 = movieService.lookForMovie("58611129-2dbc-4a81-a72f-77ddfc1b1b49");
CompletableFuture<MovieModel> page2 = movieService.lookForMovie("2baf70d1-42bb-4437-b551-e5fed5a87abe");
CompletableFuture<MovieModel> page3 = movieService.lookForMovie("4e236f34-b981-41c3-8c65-f8c9000b94e7");
// Join all threads so that we can wait until all are done
CompletableFuture.allOf(page1, page2, page3).join();
// Print results, including elapsed time
LOG.info("Elapsed time: " + (System.currentTimeMillis() - start));
LOG.info("--> " + page1.get());
LOG.info("--> " + page2.get());
LOG.info("--> " + page3.get());
}
}
ランダムに選択した映画IDを使用して、RestTemplateを使ってサンプルAPIにアクセスしました。アプリケーションを実行して、表示される出力を確認します。
アプリを実行する (Apuri o jikkō suru)
アプリケーションを実行すると、次のような出力が表示されます。
2018-04-13 INFO 17868 --- [JDAsync-1] c.j.a.MovieService : Looking up Movie ID: 58611129-2dbc-4a81-a72f-77ddfc1b1b49
2018-04-13 08:00:09.518 INFO 17868 --- [JDAsync-2] c.j.a.MovieService : Looking up Movie ID: 2baf70d1-42bb-4437-b551-e5fed5a87abe
2018-04-13 08:00:12.254 INFO 17868 --- [JDAsync-1] c.j.a.MovieService : Looking up Movie ID: 4e236f34-b981-41c3-8c65-f8c9000b94e7
2018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner : Elapsed time: 4056
2018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner : --> MovieModel{title='My Neighbor Totoro', producer='Hayao Miyazaki'}
2018-04-13 08:00:13.565 INFO 17868 --- [main] c.j.a.ApplicationRunner : --> MovieModel{title='Castle in the Sky', producer='Isao Takahata'}
2018-04-13 08:00:13.566 INFO 17868 --- [main] c.j.a.ApplicationRunner : --> MovieModel{title='Only Yesterday', producer='Toshio Suzuki'}
もし注意深く観察すれば、アプリで実行されるのはJDAsync-1とJDAsync-2のみです。
結論
このレッスンでは、Spring Boot 2でSpringの非同期機能をどのように使用するかを学びました。Springに関連する他の記事はこちらをご覧ください。
ソースコードをダウンロードしてください。
スプリングブートの非同期エグザンプルプロジェクトをダウンロードしてください。