blog

非同期タスクの SpringBootシリーズ @Asyncチュートリアル

クラスに対してこのアノテーションを記述した後、@Asyncアノテーションはクラス内に存在しないフィールドを無視します。 非同期実行の結果を取得するためにFutureを使用する場合、それがtrueである...

Nov 2, 2020 · 8 min. read
シェア

実験環境の準備

  • JDK 1.8
  • SpringBoot 2.2.1
  • Maven 3.2+
  • Maven 3.2+

pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://..org/POM/..0" xmlns:xsi="http://..org/2001"/XMLSchema-instance"
 xsi:schemaLocation="http://..org/POM/..0 https://..org/xsd/maven-...xsd">
 <modelVersion>4.0.0</modelVersion>
 <parent>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-parent</artifactId>
 <version>2.2.1.RELEASE</version>
 <relativePath/> <!-- lookup parent from repository -->
 </parent>
 <groupId>com.example.springboot</groupId>
 <artifactId>springboot-async</artifactId>
 <version>0.0.1-SNAPSHOT</version>
 <name>springboot-async</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.projectlombok</groupId>
 <artifactId>lombok</artifactId>
 <optional>true</optional>
 </dependency>
 <dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-test</artifactId>
 <scope>test</scope>
 <exclusions>
 <exclusion>
 <groupId>org.junit.vintage</groupId>
 <artifactId>junit-vintage-engine</artifactId>
 </exclusion>
 </exclusions>
 </dependency>
 </dependencies>
 <build>
 <plugins>
 <plugin>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-maven-plugin</artifactId>
 </plugin>
 </plugins>
 </build>
</project>

githubユーザー情報クラス

@JsonIgnoreProperties(ignoreUnknown = true)、このアノテーションをクラスに記述すると、クラス内に存在しないフィールドは無視され、現在のニーズに合わせて使用できるようになります。

package com.example.springboot.async.bean;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
import java.io.Serializable;
/**
 * <pre>
 * ユーザー情報エンティティ・クラス
 * Copy @ https://.io/guides/gs/async-method/
 * </pre>
 *
 * <pre>
 * 修正ログ
 * 修正版: 更新日時: 2020/07/20 10:14 修正:
 * </pre>
 */
@JsonIgnoreProperties(ignoreUnknown = true)
@Data
public class User implements Serializable {
 private String name;
 private String blog;
 @Override
 public String toString() {
 return "User{" +
 "name='" + name + '\'' +
 ", blog='" + blog + '\'' +
 '}';
 }
}

非同期タスク構成クラス

@Bean(name = "threadPoolTaskExecutor")AsyncConfigurerSupport クラスを実装することもできますし、スレッド・プールの構成を定義する , のメソッドをここで使用することもできます。

package com.example.springboot.async.config;
import com.example.springboot.async.exception.CustomAsyncExceptionHandler;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.scheduling.annotation.AsyncConfigurerSupport;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.Executor;
/**
 * <pre>
 * AsyncConfiguration
 * Copy @https://.io/guides/gs/async-method/
 * </pre>
 *
 * <pre>
 * 修正ログ
 * 修正版: 更新日時: 2020/07/20 10:12 修正:
 * </pre>
 */
@Configuration
@EnableAsync
public class AsyncConfiguration extends AsyncConfigurerSupport {
 @Override
 public Executor getAsyncExecutor() {
 ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
 executor.setCorePoolSize(2);
 executor.setMaxPoolSize(2);
 executor.setQueueCapacity(500);
 executor.setThreadNamePrefix("GithubLookup-");
 executor.initialize();
 return executor;
 }
 /*@Bean(name = "threadPoolTaskExecutor")
 public Executor threadPoolTaskExecutor() {
 return new ThreadPoolTaskExecutor();
 }*/
 
}

githubユーザー情報クエリビジネスクラス

Futureを使用して非同期実行の結果を取得する場合、ブロッキングメソッドget()を呼び出すか、isDone()がtrueかどうかをポーリングします。

Java 8では、CompletableFutureは、非同期プログラミングの複雑さを単純化するのに役立つFutureの非常に強力な拡張を提供し、コールバックを通じて計算結果を処理する関数型プログラミングの機能と、CompletableFutureを変換および結合するメソッドを提供します。

package com.example.springboot.async.service;
import com.example.springboot.async.bean.User;
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;
import java.util.concurrent.Future;
/**
 * <pre>
 * GitHubLookupService
 * copy @https://.io/guides/gs/async-method/
 * </pre>
 *
 * <pre>
 * 修正ログ
 * 修正版: 更新日時: 2020/07/20 10:18 修正:
 * </pre>
 */
@Service
public class GitHubLookupService {
 private static final Logger LOG = LoggerFactory.getLogger(GitHubLookupService.class);
 private final RestTemplate restTemplate;
 public GitHubLookupService(RestTemplateBuilder restTemplateBuilder) {
 this.restTemplate = restTemplateBuilder.build();
 }
 @Async
 //@Async("threadPoolTaskExecutor")
 public Future<User> findUser(String user) throws InterruptedException {
 LOG.info("Looking up " + user);
 String url = String.format("https://..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);
 }
}

スタートアップ・テスト・クラスの実装

SpringBootの起動時に自動的に呼び出されるCommandLineRunnerインターフェイスを実装するか、@Orderを使って実行順序を指定します。

package com.example.springboot.async.service;
import com.example.springboot.async.bean.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import java.util.concurrent.Future;
/**
 * <pre>
 * CommandLineRunner
 * Copy @https://.io/guides/gs/async-method/
 * </pre>
 *
 * <pre>
 * 修正ログ
 * 修正版: 更新日時: 2020/07/20 10:25 修正:
 * </pre>
 */
@Component
public class AppRunner implements CommandLineRunner {
 private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);
 @Autowired
 GitHubLookupService gitHubLookupService;
 @Override
 public void run(String... args) throws Exception {
 // Start the clock
 long start = System.currentTimeMillis();
 // Kick of multiple, asynchronous lookups
 Future<User> page1 = gitHubLookupService.findUser("PivotalSoftware");
 Future<User> page2 = gitHubLookupService.findUser("CloudFoundry");
 Future<User> page3 = gitHubLookupService.findUser("Spring-Projects");
 // Wait until they are all done
 while (!(page1.isDone() && page2.isDone() && page3.isDone())) {
 Thread.sleep(10); //10-millisecond pause between each check
 }
 // Print results, including elapsed time
 logger.info("Elapsed time: " + (System.currentTimeMillis() - start));
 logger.info("--> " + page1.get());
 logger.info("--> " + page2.get());
 logger.info("--> " + page3.get());
 }
}

カスタム非同期タスク例外

メソッドの戻り値の型がFutureの場合、例外処理は簡単です - Future.get()メソッドは例外を発生させます。しかし、戻り値の型が void の場合、例外は呼び出し元のスレッドに伝搬されません。そのため、例外を処理するための設定を追加する必要があります。AsyncUncaughtExceptionHandler インターフェースを実装することで、カスタム非同期例外ハンドラを作成します。

package com.example.springboot.async.exception;
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
import java.lang.reflect.Method;
/**
 * <pre>
 * Copy @ https://..com/spring-async
 * </pre>
 *
 * <pre>
 * 修正ログ
 * 修正版: 更新日時: 2020/07/20 11:08 修正:
 * </pre>
 */
public class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
 @Override
 public void handleUncaughtException(
 Throwable throwable, Method method, Object... obj) {
 System.out.println("Exception message - " + throwable.getMessage());
 System.out.println("Method name - " + method.getName());
 for (Object param : obj) {
 System.out.println("Parameter value - " + param);
 }
 }
}

カスタム非同期例外ハンドラは、getAsyncUncaughtExceptionHandler() メソッドが以下を返すようにオーバーライドする必要があります。

@Configuration
@EnableAsync
public class AsyncConfiguration extends AsyncConfigurerSupport {
 	// ...
 @Override
 public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
 return new CustomAsyncExceptionHandler();
 }
}

起動後、マルチスレッドアプローチなので、メインスレッドではなく、非同期実行の

Asyncアノテーションをコメントアウトして再度起動すると、アプリケーションがメインスレッドで実行されていることがわかります。

コード例のダウンロード:

Read next

iOSパーソナル、コーポレート、エンタープライズ証明書

開発者の信頼証明書。 cer証明書、アプリケーションパッケージ名、デバイスIDが含まれています。 のみ、このファイルをインストールXcodeはデバッグすることができます(注:ここでそれがあれば、それは実際のマシン上で使用できません...

Nov 2, 2020 · 3 min read