1. 概要

このチュートリアルでは、Springでの非同期実行のサポートと@Asyncアノテーションについて説明します。

簡単に言うと、Beanのメソッドに @Async アノテーションを付けると、別のスレッドで実行されます。つまり、呼び出し元は呼び出されたメソッドの完了を待ちません。

Springの興味深い側面の1つは、フレームワークでのイベントサポートが、必要に応じて非同期処理もサポートしていることです。

2. 非同期サポートを有効にする

始めましょう非同期処理を有効にする Java構成。

これを行うには、@EnableAsyncを構成クラスに追加します。

@Configuration
@EnableAsync
public class SpringAsyncConfig { ... }

enableアノテーションで十分です。 ただし、構成にはいくつかの簡単なオプションもあります。

  • アノテーション– デフォルトでは、@EnableAsyncはSpringの@AsyncアノテーションとEJB3.1javax.ejb.Asynchronousを検出します。 このオプションを使用して、他のユーザー定義の注釈タイプも検出できます。
  • モードは、使用する必要があるアドバイスのタイプ(JDKプロキシベースまたはAspectJウィービング)を示します。
  • proxyTargetClass は、使用する必要のあるプロキシのタイプ(CGLIBまたはJDK)を示します。 この属性は、modeAdviceMode.PROXYに設定されている場合にのみ有効です。
  • order は、AsyncAnnotationBeanPostProcessorが適用される順序を設定します。 デフォルトでは、既存のすべてのプロキシを考慮に入れることができるように、最後に実行されます。

task 名前空間を使用して、XML構成で非同期処理を有効にすることもできます。

<task:executor id="myexecutor" pool-size="5"  />
<task:annotation-driven executor="myexecutor"/>

3. @Asyncアノテーション

まず、ルールを見ていきましょう。 @Asyncには2つの制限があります。

  • publicメソッドにのみ適用する必要があります。
  • 自己呼び出し—同じクラス内から非同期メソッドを呼び出す—は機能しません。

理由は単純です。プロキシできるように、メソッドはパブリックである必要があります。 また、自己呼び出しは機能しません。プロキシをバイパスし、基になるメソッドを直接呼び出すためです。

3.1. ボイドリターン型のメソッド

これは、voidreturn型のメソッドを非同期で実行するように構成する簡単な方法です。

@Async
public void asyncMethodWithVoidReturnType() {
    System.out.println("Execute method asynchronously. " 
      + Thread.currentThread().getName());
}

3.2. リターンタイプのメソッド

@Async を、将来の実際のリターンをラップすることにより、リターンタイプのメソッドに適用することもできます。

@Async
public Future<String> asyncMethodWithReturnType() {
    System.out.println("Execute method asynchronously - " 
      + Thread.currentThread().getName());
    try {
        Thread.sleep(5000);
        return new AsyncResult<String>("hello world !!!!");
    } catch (InterruptedException e) {
        //
    }

    return null;
}

Springは、Futureを実装するAsyncResultクラスも提供します。 これを使用して、非同期メソッド実行の結果を追跡できます。

次に、上記のメソッドを呼び出し、Futureオブジェクトを使用して非同期プロセスの結果を取得しましょう。

public void testAsyncAnnotationForMethodsWithReturnType()
  throws InterruptedException, ExecutionException {
    System.out.println("Invoking an asynchronous method. " 
      + Thread.currentThread().getName());
    Future<String> future = asyncAnnotationExample.asyncMethodWithReturnType();

    while (true) {
        if (future.isDone()) {
            System.out.println("Result from asynchronous process - " + future.get());
            break;
        }
        System.out.println("Continue doing something else. ");
        Thread.sleep(1000);
    }
}

4. エグゼキュータ

デフォルトでは、Springは SimpleAsyncTaskExecutor を使用して、これらのメソッドを実際に非同期で実行します。 ただし、アプリケーションレベルまたは個々のメソッドレベルの2つのレベルでデフォルトをオーバーライドできます。

4.1. メソッドレベルでエグゼキュータをオーバーライドする

構成クラスで必要なエグゼキュータを宣言する必要があります。

@Configuration
@EnableAsync
public class SpringAsyncConfig {
    
    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        return new ThreadPoolTaskExecutor();
    }
}

次に、@Asyncの属性としてエグゼキュータ名を指定する必要があります。

@Async("threadPoolTaskExecutor")
public void asyncMethodWithConfiguredExecutor() {
    System.out.println("Execute method with configured executor - "
      + Thread.currentThread().getName());
}

4.2. アプリケーションレベルでエグゼキュータをオーバーライドする

構成クラスは、AsyncConfigurerインターフェースを実装する必要があります。 したがって、 getAsyncExecutor()メソッドを実装する必要があります。 ここでは、アプリケーション全体のエグゼキュータを返します。 これは、@Asyncで注釈が付けられたメソッドを実行するためのデフォルトのエグゼキュータになります。

@Configuration
@EnableAsync
public class SpringAsyncConfig implements AsyncConfigurer {
    
    @Override
    public Executor getAsyncExecutor() {
        return new ThreadPoolTaskExecutor();
    }
    
}

5. 例外処理

メソッドの戻り型がFutureの場合、例外処理は簡単です。 Future.get()メソッドは例外をスローします。

ただし、戻りタイプが void の場合、例外は呼び出し元のスレッドに伝播されません。したがって、例外を処理するための構成を追加する必要があります。

AsyncUncaughtExceptionHandler インターフェイスを実装して、カスタム非同期例外ハンドラーを作成します。 handleUncaughtException()メソッドは、キャッチされていない非同期例外がある場合に呼び出されます。

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);
        }
    }
    
}

前のセクションでは、構成クラスによって実装されたAsyncConfigurerインターフェイスについて説明しました。 その一環として、 getAsyncUncaughtExceptionHandler()メソッドをオーバーライドして、カスタム非同期例外ハンドラーを返す必要もあります。

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
    return new CustomAsyncExceptionHandler();
}

6. 結論

この記事では、Springで非同期コードを実行する方法について説明しました。

それを機能させるために、非常に基本的な構成とアノテーションから始めました。 ただし、独自のエグゼキュータや例外処理戦略の提供など、より高度な構成についても検討しました。

いつものように、この記事で紹介されている完全なコードは、GitHubから入手できます。